iOS引用计数

2020-10-27  本文已影响0人  Jacob6666

前情提要:

引用计数内存管理:

正文内容:

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

使用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下分析:

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


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

{
  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。)

{
  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表中,一个键,可以对应多个值。

{
  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可能会造成悬垂指针。

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.须遵守内存管理的方法命名规则

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

上一篇 下一篇

猜你喜欢

热点阅读