Objective-C内存管理浅析
Objective-C 提供3中管理记内存的方式:
automatic garbage collection: 只有 Mac OS 提供, 且在 OS X 10.8 后宣告 deprecated, 所以不该继续使用。
manual reference counting: 通过一套订好的 API 和规定函式名称代表的语意, 让开发者比较容易管理 reference counting。
automatic reference counting: 通过 compiler 生成 reference counting 相关的程式码, 开发者不用那么留意 reference counting, 看起来相当美好, 作者极力赞赏这个机制。
Manual Reference Counting (MRC)
NSObject 提供 retain 和 release 两个方法来 +1 / -1 reference count。所有物件都继承自 NSObject。开发者要记得如下的规则:
拥有物件的人记得要 +1, 不用时记得要 -1
count 为 0 时会呼叫 dealloc, 开发者可自订 dealloc 释放自己额外拥有的物件
谁配置这物件, 就是它的初始拥有者
从 copy, mutableCopy, alloc, new 取得的物件, 已经 +1 过了, 不用时记得呼叫 release。有些文章指出有唿叫 init* 开头的方法表示有 +1, 反之则没有。像是 [[NSString alloc] initWithUTF8String:X] 之于 [NSString stringWithUTF8String:X] 。这个说法不太正确, 应该是看 alloc 不是看 init。[NSString stringWithUTF8String:X] 是 class method, 不是 instance method, 所以 caller 自己没有唿叫到 alloc, 自然也没义务唿叫 release
搭配 @autoreleasepool 可使用 [X autorelease] 先标记「之后记得 -1」。好处是唿叫方法 A 会传回新物件时, 可在回传前呼叫 autorelease, 表示方法 A 没有拥有这个新物件, 且不会让新物件在传回前就被消灭。在 @autoreleasepool 的区块结束时, 会让所有呼叫过 autorelease 的物件呼叫 release。所以 [NSString stringWithUTF8String:X] 就是呼叫 alloc, 接着呼叫 autorelease, 再传回新的 NSString
可用 @property(nonatomic, retain) 表示设值的时候, 顺便在目标物件上呼叫 retain。第二个参数预设是 assign, 表示没有只有设 object reference, 没做额外动作 (如呼叫 retain)
另外两个和记忆体相关的要点是:
对 nil 呼叫任何方法, 相当于 NOP, 不用担心程式会挂掉。所以 [nil release] 也是合法的。
从 heap 配置的资料都会初始化为 0, 所以若 instance fields 有指标的时候, 其值皆为 nil。
整体来说, 清楚规则后写起会满顺手的, 不过需要点时间理清这些规则, 还有因为不是由 compiler 或 runtime 强制处理, 犯错时不易察觉。
比方说使用 @property(nonatomic, retain) str 时, 在 initStr:s 裡要怎么设才对?
@interface MyClass : NSObject
@property(nonatomic, retain) NSString *str;
@end
@implementation MyClass
@synthesize str = _str;
-(NSString*) initStrA:(NSString*)s
{
// 错误, 没有 +1, 之后需要加一行 [_str retain];
_str = s;
return self;
}
-(NSString*) initStrB:(NSString*)s
{
// 错误, 多加了一次
self.str = s;
[_str retain];
return self;
}
-(NSString*) initStrC:(NSString*)s
// 正确, 唿叫到 synthesize 产生的 setter,
// 会在设值前唿叫 [s retain] 和 [_str release], 再做 _str = s
self.str = s;
return self;
}
备注
写这篇时手边没有 Mac 环境可编译上述的程式, GNUStep 在 Linux 下编译的结果会有些错误, 但是错误来自于对 @property 和 @synthesize 的支援程度不同于 XCode 4.5。也因为这样碰巧明白 Objective-C 可能不方便跨平台, 在 Mac OS、iOS 以外的平台, 写起来可能不会很愉快。
Automatic Reference Counting (ARC)
编译时使用 ARC 的情况下, 除了 compiler 会在 "=" 出现时自动帮右边 +1, 左边塬始的值 -1 外, compiler 也会知道用 copy, mutableCopy, alloc, new 等方法取得的物件已 +1, 而帮你管对记忆体。除此之外, 还多了 strong/weak pointer 的用法。
想像有个 UI 的 view A, 裡面有个 sub view B (比方说一个视窗裡面有张图)。A 自然会有 B 的 reference, 而 B 有 A 的 reference 的话, 比较方便执行一些 callback 动作。于是, A 和 B 互指对方 (互相帮对方 +1), 即使没有任合其它物件用到 A 和 B 时, A、B 仍然不会被回收。
weak pointer 的特色是它不会列入计数, strong pointer (预设) 才会计数。所以, 若 A 用 strong pointer 拥有 B, 而 B 用 weak pointer 拥有 A。那么, 没有物件拥有 A 的时候, A 会被回收。此时, weak pointer 的值会自动设为 nil, 所以之后 B 在作操作时可检查是否为 nil 了解被回收掉了, 或是不检查直接操作 weak pointer, 也不会让程式挂掉。当然, 以这例子来说, A 的 dealloc 应该会减少 B 的计数, 若没有其它物件拥有 B 的话, B 也会被回收掉。
心得
像这样用 compiler 协助生成程式码管理 reference counting 颇妙的, 同时灭少 runtime 复杂度和开发者的负担 (compiler 的心声: 都没人在意 compiler 的工作负担啦...)。让我对 reference counting 有不同过去死板的认知。
此外, 多了 strong 和 weak pointer 的语意, 减少产生 circular reference 的可能性, 并降低物件消灭继而回收拥有权的难度。举例来说, 若物件 O 註册「当 S 有变化时, 记得呼叫 O」。那么, S 使用 weak pointer 储存 O 的话, 可以让 O 在不需拥有 S 的情况下, 自由地于任何时段灭亡, 只要 S 在呼叫 O 时先检查 pointer 是否变为 nil 即可 (multi-thread 的话, 仍需要 lock 辅助就是了)。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
