iOS引用计数

前情提要:

引用计数内存管理:
  • 自己生成对象自己持有;
  • 非自己生成的对象,自己也能持有;
  • 不再需要自己持有的对象时释放;
  • 非自己持有的对象无法释放。
  • 生成并持有对象: alloc/new/copy/mutableCopy & allocMyObject/newThatObject/copyThis/mutableCopyYourObject;持有对象:retain;释放对象:release;废弃对象:dealloc,这些有关OC的内存管理方法,不在OC语言中,而是包含在Cocoa框架中用于OS XiOS开发。由NSObject类负责。

正文内容:

自己生成的对象自己持有:

使用alloc/new/copy/mutableCopy的都符合这一条规则。copy方法利用基于NSCopying协议,由各类实现的copyWithZone:方法生成并持有对象的副本。与copy方法类似,mutableCopy方法利用基于NSMutableCopying协议,由各类实现的mutableCopyWithZone:方法生成并持有对象的副本。两者的区别在于,copy方法生成不可变更的对象,而mutableCopy生成可变更的对象。这类似于NSArray类对象与NSMutableArray类对象的差异。用这些方法生成的对象,虽然是对象的副本,但同alloc,new,方法一样,在"自己生成并持有对象"这一点上没有改变。

非自己生成的对象,自己也能持有:

通过reatain方法,非自己生成的对象也可以被自己持有。

不再需要自己持有的对象时释放:

自己持有的对象,一旦不再需要,持有者有义务释放该对象。通过release方法。
用某个方法生成对象,将alloc生成的对象原封不动地返回给调用方,这样调用方也会持有该对象,即取得了非自己生成的对象,但持有的它:

- (id)allocObject {
    id obj = [[NSObject allc] init];
    return obj;
}
id obj1 = [obj0 allocObject];

调用方不持有对象,仅引用该对象:

- (id)allocObject {
    id obj = [[NSObject allc] init];
    [obj autorelease];
    return obj;
}
id obj2 = [obj0 allocObject];

autorelease方法可以使对象存在,但obj2不持有该对象。autorelease可以使对象在超出指定的生存范围时能够自动并正确地释放。

非自己持有的对象自己无法释放:

对于使用alloc/new/copy/mutableCopy方法生成并持有的对象,或者用retain持有的对象,由于持有者是自己,所以在不需要时一定要释放。而其他情况下,一定不能释放非自己持有的对象。比如上面例子的obj2就不可以调用release不然程序会崩溃(autorelease帮他搞定)。


关于alloc/retain/release/dealloc的操作:

GNUstep下分析:
  • alloc:OC的对象存有引用计数,位置就在申请的内存块的头部,申请的内存空间为1。下面就是对象的结构体。调用类的alloc方法,会在内存中申请一块空间,包括存放整数retained(引用计数)的结构体以及对象的空间,最后返回对象的地址;
  • retain:通过对象的地址,找到该对象存放整数retained的结构体的地址,然后进行retained的+1操作;
  • release: 通过对象的地址,找到该对象存放整数retained的结构体的地址,进行-1操作,不过会有一个判断,如果retained已经是0了,那么会调用对象的dealloc方法;
  • dealloc:直接把对象free掉。

苹果下分析(用调用的类来分析):

  • alloc: +alloc -> +allocWithZone: -> class_createInstance -> callocGNU下并无太大差别;
  • retain: -retain ->__CFDoExternRefOperation -> CFBasicHashAddValue;
  • release: -release -> __CFDoExternRefOperation -> CFBasicHashRemoveValue(返回0时 调用dealloc);
  • retainCount: -retainCount -> __CFDoExternRefOperation -> CFBasicHashGetCountOfKey;
    retain/release/reatinCount都调用了一个函数__CFDoExternRefOperation,根据传入的对象,维护一个散列表来管理引用计数,并且根据传入对象的不同,分发执行,比如retain就执行retain..。这个散列表,键为对象内存块地址的散列值,值为引用计数。
    比较两种对于引用计数不同的存储方式,GNUstep的优点在于,少量代码即可完成,能够统一管理引用计数用的内存块和对象用的内存块。苹果的优点在于,对象用的内存块的分配无需考虑内存块头部,引用计数表的记录中存有内存块索引,可从各个记录追溯到各个对象的内存块。即使出现内存故障,只要引用技术表没问题,就能够确认各内存块的位置。另外,在利用工具检测内存泄露时,引用记述表的各记录也有助于检测各对象的持有者是否存在。

autorelease

GNUStep的实现:

autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法。
NSAutoreleasePool会取得正在使用的NSAutoreleasePool对象,然后把调用autorelease实例方法的对象,添加到NSAutoreleasePool对象(pool)中。实际上GNUStep使用的是链接列表,这同在NSMutableArray中追加对象参数时一样的。如果调用NSObject类的autorelease实例方法,该对象将被追加到正在使用的NSAutoreleasePool对象中的数组里。[pool drain]是将数组中所有对象release,再release掉数组的操作。

苹果的实现:

GNUStep的实现大同小异。可以通过NSAutoreleasePool类中的调试用非公开类方法showPods来确认已被autorelease的对象的状况。showPods会将现在的NSAutoreleasePool的状况输出到控制台。或者运行时方法_objc_autoreleasePoolPrint()。如果autorelese->NSAutoreleasePool对象,会发生异常,因为无论我们调用哪个对象的autorelese方法,实际上调用的都是NSObject类的autorelese实例方法。但是对于NSAutoreleasePool类,autorelese实例方法已被该实例重载,因此运行时会就出错。


ARC规则:

ARC由编译器进行管理,并且还需要OC运行时库的协助。所有权修饰符:

OC编程中,为了处理对象,可将变量类型定义为id类型或各种对象类型。所谓对象类型就是指向NSObject这样的OC类的指针。所有权修饰符有:__strong,__weak,__unsafe_unretained,__autoreleasing

  • __strong: 是id类型和对象类型默认的所有权修饰符。附有__strong修饰符的变量obj在超出其变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。__strong表示对对象的“强引用”,持有强引用的变量在超出其作用域时被废弃,随之的是强引用的失效,引用的对象没有被任何变量强引用,随之释放。OC类的成员变量,也可以在方法参数上,使用附有__strong修饰符。另外__strong,__weak,__autorelesing 都可以保证,这些修饰符修饰的自动变量初始化为nil
    例:1 .m文件如下
{
  id __strong obj = [[NSObject alloc] init];
}

汇编输出的模拟源码:

id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
//大括号外 超出obj作用域,由编译器自动插入
objc_release(obj);

2-.m文件如下

{
  id __strrong obj = [NSMutableArray array];
}

汇编输出的模拟源码:

id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

objc_retainAutoreleasedReturnValue方法主要用于程序最优化运行。它是用于自己持有对象的函数,但它持有的对象应为,返回注册在autoreleasepool中的,对象的方法,或是函数返回值。如例子中的,在调用alloc/new/copy/mutableCopy以外的方法之后,由编译器插入。与之对应的方法是objc_autoreleasedReturnValue,它用于alloc/new/copy/mutableCopy方法以外的返回对象的实现上。比如例3(NSMutableArray类的array类方法)。
3-.m文件如下

+ (id) array {
    return [NSMutableArray array];
}

汇编输出的模拟源码:

+ (id) array {
    id obj = objc_msgSend(NSMutableArray, @selector(array));
    objc_autoreleasedReturnValue(obj);
}

像该源代码这样,返回注册到autoreleasepool中对象的方法(+array())使用了objc_autoreleasedReturnValue函数返回注册到autoreleasepool中的对象(obj)。objc_autoreleasedReturnValueobjc_autorelease不同,一般不仅限于注册对象到autoreleasepool中。
通过objc_autoreleasedReturnValueobjc_retainAutoreleasedReturnValue的配合,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。(达成条件就是objc_autoreleasedReturnValue后面紧接着调用了objc_retainAutoreleasedReturnValue。)

  • __weak:因为带有__strong修饰符的成员变量在持有对象时会造成循环引用,进而引发内存泄露,就是应当废弃的对象在超出其生存周期后仍然存在,__weak可以解决这个问题。它还有一个优点,当持有对象的对象被废弃时,此弱引用将自动失效且处于nil被赋值的状态。使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象。
    例1-.m文件:
{
  id __weak obj1 = obj;
}(假设obj附加__strong修饰符且对象被赋值)

汇编输出的模拟源码:

id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

通过objc_initWeak函数初始化附有__weak修饰符的变量,通过objc_destroyWeak函数在其变量作用域结束时,释放该变量。objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象(obj)作为参数调用objc_storeWeak函数。

obj1 = 0;
objc_storeWeak(&obj1, obj);

objc_destroyWeak函数将0作为参数调用objc_storeWeak函数。

objc_storeWeak(&obj1, 0);

objc_storeWeak函数把第二参数的赋值对象(obj)的地址作为键,将第一个参数(obj1)的地址(&obj1)注册到Weak表中作为值。如果第二参数为0,把该变量(obj1)在Weak表的地址(&obj1)删除。因为一个对象可能被不同的__weak修饰符修饰的变量所引用,所以Weak表中,一个键,可以对应多个值。

  • 内存中的对象被废弃时,最后会调用objc_clear_deallocation函数:
    1.从weak表中获取废弃对象的地址为键的记录;
    2.将包含在记录中的所有附有__weak修饰符变量的地址(值),赋值为nil;
    3.从weak表中删除该记录;
    4.从引用计数表中删除废弃对象的地址为键的记录。
    由此可知,如果大量使用__weak会消耗对应的CPU资源,良策是只在避免循环引用时使用__weak修饰符。
  • __weak修饰符的变量,即是使用注册到autoreleasepool中的对象:
    .m文件
{
  id __weak obj1 = obj;
NSLog(@"%@", obj1);
}

汇编输出的模拟源码:

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

增加了对objc_loadWeakRetainedobjc_autorelease函数的调用,这些函数的动作如下:
objc_loadWeakRetained函数取出附有__weak修饰符变量所引用的对象并retain
objc_autorelease函数将对象注册到autoreleasepool中。
由此可知,因为附有__weak修饰符变量所引用的对象像这样被注册到autoreleasepool中,所以在@autoreleasepool块结束之前都可以放心使用。但是如果像上面那样大量使用obj1变量,就会有给autoreleasepool造成很大的压力,所以最好先暂时赋值给附有__strong修饰符的变量后再使用。比如 id strongTmp = obj1;之后使用strongTmp就不会有这个问题。
注意一下,iOS4以下,NSMachPort类,allowsWeakReference, retainWeakReference返回NO的,均不支持__weak。如果重写了allowsWeakReference, retainWeakReference方法,里面操作运行时库时,会引发死锁,因为运行时库在处理__weak时,调用了这些方法。
__unsafe_unretained:iOS4之前是没有__weak的所以要用__unsafe_unretained来达成__weak做的事,但用__unsafe_unretained修饰的变量不属于编译器的内存管理对象。而且使用__unsafe_unretained可能会造成悬垂指针。

  • __autoreleasing: ARC有效时,这样用@autoreleaapool {...}。另外,使用__autoreleasing修饰符来修饰变量,达到调用autoreleas方法的目的。但一般不会显式赋值__autoreleasing修饰符,因为编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleaspool中。显示调用,一般在id的指针或是对象的指针,比如NSError的方法。并且要注意,赋值给对象的指针时,所有权修饰符必须要一致。例(NSError):
NSError *error = nil;
BOOL result = [obj performOperationWithError: (NSError **)&error];
- (BOOL)performOperationWithError:(NSError: * __autoreleasing *)error;

例(__autoreleasing):.m

@autoreleasepool {
    id __autoreleasing obj = [[NSObject alloc] init];
}

汇编输出的模拟源码:

id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

返回alloc/new/copy/mutableCopy方法群之外的方法中,注册到autoreleasepool中的对象:.m

@autoreleasepool {
    id __autoreleasing obj = [NSMutableArray array];
}

汇编输出的模拟源码:

id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_msgSend(obj, @selector(init));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

属性:

当ARC有效时,OC类的属性也会发生变化assign: __unsafe_unretained;copy:__strong;retain:__strong;strong:__strong;unsafe_unretained:__unsafe_unretained;weak:__weak
只有copy属性不是简单的赋值,它赋值的是通过NSCopying接口的copyWithZone:方法复制赋值源所生成的对象。


总结:

ARC有效的情况下编译源代码,必须遵守一定的规则。
1.不能使用retain/release/retainCount/autorelease
2.不能使用NSAllocateObject/NSDeallocateObject
3.须遵守内存管理的方法命名规则

  • init方法开头的,要求该方法必须是实例方法,且必须返回对象。该返回对象并不注册到autoreleasepool上.基本上只是对alloc方法返回值的对象进行初始化处理并返回处理好之后的对象。
    4.不要显式调用dealloc
    5.使用@autoreleaapool块代替NSAutoreleasePool
    6.不能使用区域(NSZone)
    7.对象类型变量不能直接作为C语言结构体成员。Block结构体中可以,原因在于编译器会增添捕获对象的copy,dispose方法,来管理该对象
    8.显示转换idvoid *
    ARC无效时可以直接转换,有效时不可以,如果单纯的想要赋值可以用__bridge:
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;

其安全性与赋值给__unsafe_unretained修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。
引申出两种其他转换方法:__bridge_retained__bridge_transfer
__bridge_retained:可使要转换赋值的变量也持有所赋值的对象,相当于给被赋值的变量retain一下,相当于控制权交给了C对象,C对象用完之后要记得CFRelease一下这个C变量。OC->C
__bridge_transfer: 被转换的变量所持有的对象在该变量被赋值给转换目标之后随之释放,相当于把OC这个变量retain一下,C这个变量CFRelease一下,就是交换了控制权。C->OC

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