前情提要:
引用计数内存管理:
- 自己生成对象自己持有;
- 非自己生成的对象,自己也能持有;
- 不再需要自己持有的对象时释放;
- 非自己持有的对象无法释放。
- 生成并持有对象:
alloc
/new
/copy
/mutableCopy
&allocMyObject
/newThatObject
/copyThis
/mutableCopyYourObject
;持有对象:retain
;释放对象:release
;废弃对象:dealloc
,这些有关OC
的内存管理方法,不在OC
语言中,而是包含在Cocoa
框架中用于OS X
,iOS
开发。由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 -> calloc
与GNU
下并无太大差别; -
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_autoreleasedReturnValue
同objc_autorelease
不同,一般不仅限于注册对象到autoreleasepool
中。
通过objc_autoreleasedReturnValue
和objc_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_loadWeakRetained
和objc_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.显示转换id
和void *
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