iOS中的block和retain cycle (经典)

本文详细介绍了Objective-C中的内存管理机制,包括引用计数原理及如何避免retaincycle问题。探讨了block与内存管理的关系,并针对ARC环境下如何解决retaincycle问题给出建议。

retain cycle 的产生,说到retain cycle,首先要提一下Objective-C的内存管理机制。

作为C语言的超集,Objective-C延续了C语言中手动管理内存的方式,但是区别于C++的极其非人道的内存管理,

Objective-C提出了一些机制来减少内存管理的难度,内存计数,即引用计数。


在Objective-C中,凡是继承自NSObject的类都提供了两种方法,retain和release。

当我们调用一个对象的retain时,这个对象的内存计数加1,反之,当我们调用release时, 对象的内存计数减1,只有

当对象内存计数为0时,这个对象才真正会被释放,此时,对象的delloc方法会被调用来做些内存回收前的工作。


内存计数机制的好处在于我们可以明确分配一个使用权。

比如,当一个对象A要使用另外一个对象B的时候,A会retain B一次以表示A使用B,而当B被使用完毕之后,A会 调用

B的release方法来放弃使用权。这样,一个对象可以被多个其他对象使用。而作为使用它的对象,也不必关心自己之

外 被使用对象的使用情况(内存方面)。一般来讲,对于类的成员变量,retain和release分别发生在赋值和自身释放

的时候,这就是Obj-C程序中的经典写法:


头文件中:
@property (nonatomic, retain) NSObject *obj;
在.m文件里:
- (void)dealloc
{
    [obj release];
    [super dealloc];
}
OK,这种方式可以很容易地管理内存,但是仍存在这一个问题,这就是retain cycle。

Retain cycle,翻译成中文大概叫保留环吧。既然父对象持有子对象,而子对象会随父对象释放而释放,那么,如果两

个对象相互为父对象怎么办?


比如A和B两个对象,A持有B,B同时也持有A,按照上面的规则,A只有B释放之后才有可能释放,同样B只有A释放

后才可能释放,当双方都在等待对方释放的时候, retain cycle就形成了,结果是,两个对象都永远不会被释放,最终

内存泄露。


retain cycle使你编程的时候不得不注意一些问题。例如,要么尽量保持子对象引用父对象的时候使用弱引用,也就是

assign,比如@property (nonatomic,assign) NSObject *parent;要么及时地将造成retain cycle中的一个变量设置为

nil,将环break掉。如果注意点,这并不是什么特别大的问题。


嗯,注意点确实不是什么问题,但是当IOS 4.0只后,block的出现,使你更需要更为谨慎。


block与内存管理

block就是一段可以灵活使用的代码,你可以把它当变量传递,赋值,甚至可以把它声明到函数体里,更灵活的是你可

以在里面引用外部的环境。 最后一条使得block要有更多的考虑,既然block可以引用外部环境,那如何保证block被调

用的时候当时的环境变量不被释放呢?(block调用的时机可能是随意的)


答案就是,被block引用的变量都会被自动retain一次,这样的话至少可以保证我们的调用是有效的。

说到这里你能想到什么吗?对,还是retain cycle。因为block中的retain是隐式的,所以极易出现retain cycle的问题。

因为block本身也可以看做一个对象,也存在生命周期,也可以被持有,所以当这种情况出现的时候,我们该注意了,

比如:

DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
    //...complete actions
    [manager otherAction];
    [manager release];
};

retain cycle 就这么形成了,即使调用了release,manager也不会释放,因为manager和block相互持有了。为了解除

retain cycle的话,我们可以这样写:

DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
    //...complete actions
    [manager otherAction];
    manager.complete = nil;
    [manager release];
};

manager的complete被设置为nil,如此一来retain cycle也被破坏掉,前提是你确实不需要再次回调block了。


本来写到这里就算完了,但是新世纪总有新的挑战,这就在于在Apple有推出了一种新的技术 ARC。

ARC 和 retain cycle

ARC (Auto Reference Counting), 翻译为自动引用计数,是Apple为了进一步简化内存管理来推出的技术。虽然为自动

内存管理而生,但却并算不上真正的自动管理。 这是因为ARC是一种编译期的技术,它所做的是自动识别你的代码并

转换成retain/release的形式,在这个层面上来看,ARC无非是简化了代码的书写,并提供了部分性能上的优化, 而并

不像Java之类的语言可以完全把垃圾回收抛之脑后(基本上)。

关于ARC的细节可以看下面的网址:

http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

下面我们主要谈下ARC下retain cycle的问题。

ARC中,变量可以用三个关键字修饰:

1、 __strong: 赋值给这个变量的对象会自动被retain一次,如果在block中引用它,block也会retain它一次。

2、 __unsafe_unretained: 赋值给这个变量不会被retain,也就是说被他修饰的变量的存在不能保证持有对象的可靠

性,它可能已经被释放了,而且留下了一个不安全的指针。不会被block retain。

3 、__week:类似于__unsafe_unretained,只是如果所持有的对象被释放后,变量会自动被设置为nil,这样更安全

些,不过只在IOS5.0以上的系统支持,同样不会被block retain。


另外我们也可以用 __block 关键字修饰一个变量,表示这个变量能在block中被修改(值修改,而不是修改对象中的某

一个属性,可以理解为修改指针的指向)。会被自动retain。

于其他变量不同的是被 __block 修饰的变量在块中保存的是变量的地址。(其他为变量的值)

首先,上面的代码你现在可以这么写:

DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
    //...complete actions
    [manager otherAction];
    manager.complete = nil;
};

没什么问题,只是去掉了ARC中禁止的release。

当然,我们也可以这么写:

__block DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
    //...complete actions
    [manager otherAction];
    manager = nil;
};

如果不用ARC,manager不会在block中被retain,但是采用了ARC就有些复杂了。block会retain manager变量,但

是,由于__block变量保存更为底层的变量地址, 因此当此变量被指向其他对象时,block便不对原来的对象负责,引

发的结果就是之前对象被release掉,retain cycle被破坏。

或者这么写:

__block DoSomethingManager *manager = [[DoSomethingManager alloc] init];
DoSomethingManager __week *weekmanager = manager;
manager.complete = ^{
    //...complete actions
    [weekmanager otherAction];
};

上面的__week也可以用 __unsafe_unretained 替代,但是 __week 更安全些,虽然它不支持IOS5.0以下的系统。

被 __week 或者 __unsafe_unretained 修饰的变量不会被block retain,所以不会形成retain cycle,但是小心,保证你

的对象不会在complete之前被释放,否则会得到你意向不到的结果。

  


(1)普通用户端(全平台) 音乐播放核心体验: 个性化首页:基于 “听歌历史 + 收藏偏好” 展示 “推荐歌单(每日 30 首)、新歌速递、相似曲风推荐”,支持按 “场景(通勤 / 学习 / 运动)” 切换推荐维度。 播放页功能:支持 “无损音质切换、倍速播放(0.5x-2.0x)、定时关闭、歌词逐句滚动”,提供 “沉浸式全屏模式”(隐藏冗余控件,突出歌词与专辑封面)。 多端同步:自动同步 “播放进度、收藏列表、歌单” 至所有登录设备(如手机暂停后,电脑端打开可继续播放)。 音乐发现与管理: 智能搜索:支持 “歌曲名 / 歌手 / 歌词片段” 搜索,提供 “模糊匹配(如输入‘晴天’联想‘周杰伦 - 晴天’)、热门搜索词推荐”,结果按 “热度 / 匹配度” 排序。 歌单管理:创建 “公开 / 私有 / 加密” 歌单,支持 “批量添加歌曲、拖拽排序、一键分享到社交平台”,系统自动生成 “歌单封面(基于歌曲风格配色)”。 音乐分类浏览:按 “曲风(流行 / 摇滚 / 古典)、语言(国语 / 英语 / 日语)、年代(80 后经典 / 2023 新歌)” 分层浏览,每个分类页展示 “TOP50 榜单”。 社交互动功能: 动态广场:查看 “关注的用户 / 音乐人发布的动态(如‘分享新歌感受’)、好友正在听的歌曲”,支持 “点赞 / 评论 / 转发”,可直接点击动态中的歌曲播放。 听歌排行:个人页展示 “本周听歌 TOP10、累计听歌时长”,平台定期生成 “全球 / 好友榜”(如 “好友中你本周听歌时长排名第 3”)。 音乐圈:加入 “特定曲风圈子(如‘古典音乐爱好者’)”,参与 “话题讨论(如‘你心中最经典的钢琴曲’)、线上歌单共创”。 (2)音乐人端(创作者中心) 作品管理: 音乐上传:支持 “无损音频(FLAC/WAV)+ 歌词文件(LRC)+ 专辑封面” 上传,填写 “歌曲信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值