[iOS]关于ARC实现的一些总结

最近在重温《Objective-C 高级编程》这本书,深深为这本薄书里蕴含的"惊人"能量所倾倒.本篇文章将总结一下ARC实现的一些细节.

ARC的概念

ARC翻译过来就是自动引用计数,相信大家都知道.苹果官方文档中说明:ARC是由由编译器进行内存管理的;本书指出了一个观点:实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行时库的协助.ARC的核心就是在对象需要释放的地方自动插入release.
说起ARC,就逃不过讨论几个修饰符:__strong,__weak,__autoreleasing和引用计数,下面一一来进行细节说明.

__strong

先看如下一段代码:

{
// ARC中默认会在对象前添加一个修饰符__strong
id obj = [[NSObject alloc] init];
//<==>等价于
id __strong obj = [[NSObject alloc] init];
}

根据runtime特性,它的实际调用如下:

{
// 消息转发
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
// 编译器在obj作用域结束时自动插入release
objc_release(obj);
}

当然这里是以alloc/new/copy/mutableCopy生成的对象,这种对象会被当前的变量所持有,引用计数会加1.那如果不是用被持有的方式生成对象呢?
看下面这段代码:

{
id obj = [NSMutableArray array];
}

这种方式生成的对象不会被obj持有,通常情况下会被注册到autoreleasepool中.但也有特殊情况,上面的代码可以转换成如下代码:

{
// 消息转发
id obj = objc_msgSend(NSMutableArray,@selector(array));
// 调用objc_retainAutoreleasedReturnValue函数
objc_retainAutoreleasedReturnValue(obj);
// 编译器在obj作用域结束时自动插入release
objc_release(obj);
}

这里介绍两个相关函数:

  • objc_retainAutoreleasedReturnValue():这个函数的作用是返回注册在autoreleasepool当中的对象.
  • objc_retainAutoreleaseReturnValue():这个函数一般是和objc_retainAutoreleasedReturnValue()成对出现的.目的是注册对象到autoreleasepool中.但不仅限于此.
    为何说不仅限于此呢?原因在于,objc_retainAutoreleaseReturnValue()函数在发现对象调用了方法或者函数之后又调用了objc_retainAutoreleasedReturnValue(),那么就不会再把返回的对象注册到autoreleasepool中了,而是直接把对象传递过去.
    这样的好处显而易见:不用再去autoreleasepool中取出对象,传递出去,而是越过autoreleasepool直接传递,提升了性能.

__weak

weak修饰符想必大家都非常熟悉,它有一个众所周知的特性:用weak修饰的对象在销毁后会被自动置为nil.另外还补充一点:凡是用weak修饰过的对象,必定是注册到autoreleasepool中的对象.
看下面的代码:

{
// obj默认有__strong修饰
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}

实际过程如下:

{
// 省略obj的实现
id obj1;
// 通过objc_initWeak初始化变量
objc_initWeak(&obj1,obj);
// 通过objc_destroyWeak释放变量
objc_destroyWeak(&obj1);
}
  • objc_initWeak()函数的作用是将obj1初始化为0,然后将obj作为参数传递到这个函数中objc_storeWeak(&obj1,obj)
  • objc_destroyWeak()函数则将0作为参数来调用:objc_storeWeak(&obj1,0)
  • objc_storeWeak()函数的作用是以第二个参数(obj || 0)作为key,第一个参数(&obj1)作为value,将第一个参数的地址注册到weak表中.当key为0,即从weak表中删除变量地址.

那么weak表中的对象是如何被释放的呢?

  • 从weak表中获取废弃对象的键值记录.
  • 将记录中所有包含__weak的变量地址,赋值为nil.
  • 从weak表中删除该记录.
  • 从引用计数表中删除对应的记录.

这就是__weak修饰的变量会在释放后自动置为nil的原因.同时,因为weak修饰之后涉及到注册到weak表等相关操作,如果大量使用weak可能会造成不必要的CPU资源浪费,所以书里指出尽量在循环引用中使用weak.
这里不得不提到另外一个和__weak相近的属性:__unsafe_unretained,它与weak的区别在于,释放对象后不会对其置为nil,在某些特定的场合下,需要延迟释放的时候,可以考虑用这个属性修饰.

好了,下一个问题,看如下代码:

{
id __weak obj1 = obj;
// 这里使用了obj1这个用weak修饰的变量
NSLog(@"%@",obj1);
}

在weak变量被使用的情况下,实际过程如下:

{
id obj1;
objc_initWeak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&obj1);
}

从这段实现代码中我们可以看出如下几点:

  • 当我们使用weak修饰的对象时,实际过程中产生了一个tmp对象,因为objc_loadWeakRetained()函数会从weak表中取出weak修饰的对象,所以tmp会对这个取出的对象进行一次强引用.
  • 因为上述原因,weak修饰的对象在当前变量作用域结束前都可以放心使用.
  • objc_autorelease()会将tmp对象也注册到autoreleasepool中.所以当大量使用weak对象的时候,注册到autoreleasepool的对象会大量增加.解决方案就是用一个__strong修饰的临时变量来使用.
{
id __weak obj1 = obj;
id tmp = obj1;
// 后面使用tmp即可
}

延伸一下:为什么有循环引用block内用weakObject的时候最好能在block内套一层strongObject?

  • 在异步线程中weakObject可能会被销毁,所以需要套一层strong.
  • 如果内部有耗时的循环语句,频繁使用weakObject也会增加内存损耗.

__autoreleasing

它的主要作用就是将对象注册到autoreleasepool中.没啥好说的.

最后补充几种在ARC环境下获取引用计数的方法,但并不一定准确:ARC的一些引用计数优化,以及多线程的中的竞态条件问题,有兴趣的可以自己去了解一下.

(1) 使用_objc_rootRetainCount()私有函数
OBJC_EXTERN int _objc_rootRetainCount(id);
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    NSLog(@"%d",_objc_rootRetainCount(obj));
}
@end

(2) 使用KVC
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    NSLog(@"%d",[[obj valueForKey:@"retainCount"] integerValue]);
}
@end

(3) 使用CFGetRetainCount()
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    NSLog(@"%d",CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容