ARC 简明参考手册 (Part 2)
引言
前面说到, ARC 简单的来说, 就是内存管理的半自动档。 使用 ARC 需要遵守一定的约定, 事实上这些约定在开启 ARC 模式下大部分是编译器强制的, 所以给新手的建议是, 找一本 ARC 后时代的书, 而不要花时间在 retain/release 这些细节上。 在这里, 我们将讨论的是, 有哪些细节是我们需要关心的, 也就是 ARC 自己没有办法处理的。
weak 属性修饰符
例如在树的实习中, 子节点如果持有父节点的强引用, 就会造成 strong reference cycle, 所以需要使用 weak 属性修饰符。
@interface TreeNode : NSObject
@property NSMutableArray* childs;
@property (weak) TreeNode* parent;
@end
同时建议委托使用 weak 属性修饰符。
@property (nonatomic, weak) NSObject <SomeDelegate> *delegate;
例如, 在 UI 中,一个 ViewController 是一个 View 的委托对象, 如果不使用 weak 修饰符, 就会造成如图所示的 S.R.C.。
__weak 变量修饰符
由于 block 捕获变量默认是强引用, 在 block 中捕获 self 有可能导致 strong reference cycle, 所以需要使用 __weak 变量修饰符。
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething]; // capturing a strong reference to self
// creates a strong reference cycle
};
}
...
@end
需要更正为:
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
block 中对 ivar 的引用也是一个陷阱:
// The following block will retain "self"
SomeBlockType someBlock = ^{
BOOL isDone = _isDone; // _isDone is an ivar of self
};
也要注意可能在 block 中捕获了外部的变量, 而这个变量又反过来持有这个 block。
SomeObjectClass *someObject = ...
__weak SomeObjectClass *weakSomeObject = someObject;
someObject.completionHandler = ^{
SomeObjectClass *strongSomeObject = weakSomeObject;
if (strongSomeObject == nil)
{
// The original someObject doesn't exist anymore.
// Ignore, notify or otherwise handle this case.
}
else
{
// okay, NOW we can do something with someObject
[strongSomeObject someMethod];
}
};
__autoreleasing 变量修饰符
用于这样的情景, 一个方法返回 BOOL 值指示错误, 并有一个 NSError ** 作为传出参数 。
(BOOL) doSomething:(NSError * __autoreleasing *)myError {
NSError *error = [[NSError alloc] init];
myError = &error;
// ...
return NO;
}
上面的代码片段的问题在于, callee 中的 error 是强引用, 而它不作为返回值, 那么从 callee 的堆栈到 caller 的堆栈的时候, 它就会被销毁。 所以需要更正如下:
NSError __autoreleasing *error = [[NSError alloc] init];
myError = &error;
或者
*myError = [[NSError alloc] init];
总结
这里的内容已经覆盖了 80% 左右的, 当你在 ARC 模式下 需要关心的问题, 还有一些高级的主题, 主要涉及非 ARC 代码(例如 Core Foundation Framework) 和 ARC 代码之间的转换, 容后再叙。