理解引用计数:
Objective-C使用引用计数来管理内存,引用技术机构下,对象有个计时器用以表示多少个事物想令此对象继续存活下去,即“引用计数”。
对象创建出来时,其保留计数至少为1,当保留计数归零时,对象所占内存就会被系统回收。
对象如果持有指向其他对象的强引用,那么前者就“拥有”后者,即被持有对象引用计数递增。如果按“引用树”回溯,会发现一个“根对象”,iOS应用程序中找个对象是UIApplication对象,是应用程序启动时创建的单例。
Objective-C 中调用 alloc 方法所返回的对象由调用者所拥有,不过这个对象的引用计数不一定就是1,在 alloc 或 initWithInt: 方法的实现代码中,也许有其他对象也持有该对象,所以应该说这个对象的引用计数至少为1。绝不应该说保留计数一定是某个值,只能说某一操作是递增了其引用计数还是递减了引用计数。
对象所占内存在解除分配(deallocated)后,只是放回可用内存池(avaiable pool),所以在该对象内存被覆写之前,该对象仍然有效,使用它不会导致程序崩溃。这就是因为过早释放对象而导致的内存问题,程序可能运行正常也可能奔溃,所以这样的bug很难调试。
解决:调用完release之后清空指针,即让指针指向 nil,这就保证不会出现指向无效对象的指针。
以ARC简化引用计数:
引用计数仍然执行,不过是由ARC自动添加保留与释放操作;ARC中不能调用以下方法:
- retain
- release
- autorelease
- dealloc
ARC在调用这些方法时,是直接调用其底层C语言版本,如:objc_retain
若方法名以下列词语开头,则其返回的对象归调用者所有,要负责释放方法返回的对象:
- alloc
- new
- copy
- mutableCopy
ARC大法好...
对于MRC部分代码,使用编译参数 -fno-objc-arc 可以对该部分文件关闭ARC。
在应用程序中可用使用下列修饰符改变局部变量与实例变量的语义:
- __strong:默认语义,保留此值。
- __unsafe_unretained:不保留此值,这么做可能不安全,因为等到再次使用该变量时,其对象可能已经被回收。
- __weak:不保留此值,但是变量可以安全使用,如果系统将对象回收,那么变量也会自动清空。
- __autoreleasing:把对象“pass by reference”给方法时,使用此修饰符,在方法返回时此值自动释放。
常需要给局部变量加上修饰符,用以打破block所引入的“保留环”,block会自动保留其所捕获的全部对象,如果其中某个对象同时又保留了block,则可能导致“保留环”。
解决:这种情况下可以使用__weak局部变量打破“保留环”。
注意:ARC只负责管理Objective-C对象的内存,CoreFoundation对象不归ARC管理,需要开发者适时调用CFRetain/CFRelease。
在dealloc方法中只释放引用并解除监听:
不要自己调用dealloc方法,在dealloc方法中只做两件事:手工释放CoreFoundation对象;把原来配置过的观测行为清理掉。
对于开销较大或系统内稀缺的资源,我们要实现另一个清理方法,当应用程序使用完资源对象后立即清理(如socket的close方法)如果没有适时调用close清理方法,则需要在dealloc中补上这次调用:
- (void)close{
/* 清理资源 */
_close = YES ;
}
- (void)dealloc{
if ( !_closed ) {
NSLog ( @“ERROR: close was not called before dealloc !” ) ;
[ self close ] ;
}
}
注意:不要在dealloc中随意调用其他方法!
以弱引用避免保留环
几个对象以某种方式相互引用,从而形成“环”,导致内存泄漏。
避免保留环的最佳方式就是弱引用,使用 weak 而非 unsafe_unretained 引用可以令代码更安全:unsafe_unretained 声明的属性会一直指向那个已经回收的实例,而 weak 声明的属性其对象被回收后,它会指向nil。
注意:使用 block 时容易产生“保留环”,因为 block 会捕获它范围里的所有变量
以“自动释放池块”降低内存峰值
自动释放池可以嵌套使用,以此控制应用程序的内存峰值使其不致过高。
示例代码:
NSMutableArray *peopleArray = [NSMutableArraynew];
for (int i = 0; i < 1000000; i++) {
EOCPerson *person = [[EOCPersonalloc] init];
[peopleArray addObject:person];
}
上述代码会不断创造出临时对象,内存中会占用很多不必要的临时对象,导致程序执行 for 循环时所占内存量持续上升,这些临时对象释放后内存又突然下降。
解决:增加一个自动释放池
NSMutableArray *peopleArray = [NSMutableArraynew]; for (int i = 0; i < 1000000; i++) { @autoreleasepool { EOCPerson *person = [[EOCPersonalloc] init]; [peopleArray addObject:person]; } }
把循环内的代码包裹在“自动释放池块”中,循环中自动释放的对象就会在这个池而不是线程的主池里了,这样应用程序在执行以上循环时内存峰值会降低。
自动释放池块有一定的开销,是否需要使用取决于具体应用程序的内存用量,如果不需要尽量不要建立额外的自动释放池。
用“僵尸对象”调试内存管理问题
之前提到被回收的内存只是被放回可用内存池中,在这块内存被其他内容覆写前仍可以接收消息,也有可能那块内存只被复用了一部分、甚至可能被另一个有效且存活的对象占据,这种情况下程序时而正常时而奔溃,收到消息的对象不是预想的对象。
为了调试这种内存问题,可以使用“僵尸对象”功能,启用该功能后,运行期系统会把所有已回收的实例转化为“僵尸对象”,而不会真正回收它们;僵尸对象如果收到消息会抛出异常,并说明发送过来的消息、描述回收前的对象。
开启“僵尸对象”:Product ——> Scheme ——> Edit Scheme ——> Diagnostics ——> Enable Zombie Objects
僵尸对象的原理好长...要点:系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变成僵尸对象。僵尸类能够响应所有selector,响应方式为:打印一条包含消息内容及其接受者的消息,然后终止应用程序。
最后:不要使用retainCount,在ARC下也无法使用。