第29条:理解引用计数
- 属性存取方法中的内存管理
- (void)setFoo:(id)foo{
[foo retain];
[_foo release];
_foo = foo;
}
此方法将保留新值并释放旧值,然后更新实例变量,令其指向新值。顺序很重要。假如还未保留新值就先把旧值释放了,而且两个值又指向同一个对象,那么,先执行的release操作就可能导致系统将此对象永久回收。而后续的retain操作则无法令这个已经彻底回收的对象复生,于是实例变量成了悬挂指针。
- 自动释放池
- 调用release会立刻递减对象的保留计数(而且还有可能令系统回收此对象)
- autorelease 方法会在稍后递减计数,通常是在下一次“事件循环”(event loop)时递减,不过也可能执行更早些(参见第34条)
- (NSString *)stringValue{
//alloc 后引用计数为1,如果initWithFormat还保留引用计数的话,str可能就被引入两次
NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return str;//这里如果str引用计数为1的话,那么也不可能调用release,不然的话,有可能返回的是nil,这里的str没有被释放
}
- (NSString *)stringValue{
NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return [str autorelease];
}
修改后,stringValue
方法把 NSString
对象返回给调用者时,此对象必然存活,所以可以如下使用:
NSString *str = [self stringValue];
NSLog(@"The string is: %@", str);
第30条:以ARC简化引用计数
- 使用ARC时必须遵循的方法命名规则
若方法名以下列的词语开头,则其返回的对象归调用者所有:
- alloc
- new
- copy
- mutableCopy
归调用者所有的意思是:调用上述四种方法的那段代码要负责释放方法所返回的对象。也就是说,这些对象的保留计数是正值,而调用了这四种方法的那段代码要将其中一次保留操作抵消掉。
若方法名不以上述四个词语开头,则表示其所返回的对象并不归调用者所有。这种情况下,返回的对象会自动释放,所以其值在跨越方法调用边界后依然有效。要想使对象多活一段时间,必须令调用者保留它才行。
- 在编译期,ARC会把能够互相抵消的retain, release, autorelease操作约简,如果发现在同一个对象上执行了多次“保留”与“释放”操作,那么ARC有时可以成对地移除这两个操作,这种操作手动是做不到的。
- 在运行期,在某些方法在返回对象前,为其执行了autorelease操作,而调用方法的代码可能需要将返回的对象保留,如下
//_persion是一个强引用的实例变量
_persion = [WCCPerson personWithName:@"zhangsan"];//此处有autorelease
该代码等效于
WCCPerson *temp = [WCCPerson personWithName:@"zhangsan"];//autorelase操作
_persion = [temp retain];
此时可以看出来,personWithName:
方法里的 autorelease
与上段代码中的retain
都是多余的。为了提升性能,可将二者删去。
- 变量的内存管理语义
默认情况下,每个变量都是指向对象的强引用。
可以给局部变量加上修饰符,用以打破由“块(block)”所引入的“保留环“,用__weak
可以做到。 - 对方法所返回的对象,其内存管理语义总是通过方法名来体现的。ARC将此确定为开发者必须遵守的规则。
- ARC只负责管理Objective-C对象的内存,尤其要注意:CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease。
第31条:在dealloc方法中只释放引用并解除监听
- 在dealloc方法里,应该做的事情就是释放指向其他对象的引用,并取消原来订阅KVO或NSNotificationCenter等通知,不要做其他事情。
- 如果对象有文件描述符等系统资源,那么应该专门编写一个方法来释放此种资源。这样的类要和其使用者约定:用完资源后必须调用close方法。
- 执行异步任务的方法不应在dealloc里调用;只能在正常状态下执行的那些方法也不应该在dealloc里调用,因为此时对象已经处于正在回收的状态了。
第32条:编写“异常安全代码”时留意内存管理问题
- 捕获异常时,一定要注意将try块内所创立的对象清理干净。
- 在默认情况下,ARC不生成安全处理异常所需的清理代码。开启编译器标志(-fobjc-arc-execptions)后,可生成这种代码,不过会导致应用程序变大,而且会降低运行效率。
第33条:以弱引用避免保留环
- 将某些引用设置为
weak
,可避免出现“保留环”。
第34条:以“自动释放池块”降低内存峰值
- 在Objective-C的引用计数架构中,释放对象有两种方式:一种是调用release方法,使其保留一计数立即递减,另一种是调用autorelease方法,将其加入“自动释放池”中,自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空自动释放池时,系统会向其中的对象发送release消息。
这两者的区别在于一个立即,一个稍后。 - 自动释放池排布在栈中,对象收到autorelease消息后 ,系统将其放入最顶端的池里。
- 合理运用自动释放池,可降低应用程序的内存峰值。
- @autoreleasepool 这种新式写法能创建出更轻便的自动释放池。
第35条:用“僵尸对象”调试内存管理问题
- 向已经回收的对象发送消息是不安全的(程序可能会崩溃),这么做有时可以,有时不可以,具体是否可行,完全取决于对象所占内存有没有为其他内容所覆写。
- Cocoa提供了“僵尸对象”(Zombie Object),启用这项调试功能后,运行期系统会把所有已经回收的实例转化成特殊的“僵尸对象”,而不会真正的回收他们。这种对象所在的核心内存无法重用,因此不可能遭到覆写。僵尸对象收到消息后,会抛出异常,其中准确说明了发送过来的消息,并描述了回收之前的那个对象。
- 僵尸对象工作原理:在运行期系统如果发现
NSZombieEnabled
环境变量已设置,那么就把dealloc方法“调配”(见第13条)。执行到程序末尾时,对象所属的类已经变成_NSZombie_OriginalClass。代码的关键之处在于:对象所占用内存没有(通过调用free()方法)释放,因此,这块内存不可复用。虽说内存泄漏了,但这只是调试手段,正式版本不会打开此功能的。 - 系统会修改对象的isa指针,令其指向特殊的僵尸类(同观察模式一样,移动isa指针),从而使对象变为僵尸对象,僵尸对象能够相应所有的选择子,响应方式为:打印一条包含消息内容及其接受者的消息,然后终止应用程序。
第36条:不要使用retainCount
- 对象的保留计数看似有用,实则不然,因为任何给定时间点上的“绝对保留计数”都无法反映对象生命期的全貌。
- 引入ARC之后,retainCount方法就正式废止了,在ARC下调用该方法会导致编译器报错。