首先需要澄清的是,只有当对象指向自身时才会出现保留环!这通常发生在对象的某个属性(obj持有该属性,该属性又持有obj等)通过块或委托强引用回对象本身。
块从外部作用域捕获传递给它们的任何对象的强引用 - 包括self。 当你声明一个块并且只执行它时并不会造成问题,因为块是在方法内声明的,ARC将在方法结束时对其进行释放:
-(void)test
{
void(^aBlock)() = ^{
[self someMethod];
};
aBlock();
}
但是,如果您将此块存储以供稍后使用(例如回调),则会出现问题,例如:
@property(nonatomic, copy) void(^aBlock)();
-(void)test
{
_aBlock = ^{
[self someMethod];
};
}
现在我们在ClassA中有了一个强引用(我们的属性aBlock),它通过aBlock实现对ClassA自身(self)的强引用。现在我们遇到了一个问题 - 这个类指向它自己,永远不会被释放!如果我们将上述重写为以下形式,情况就更明显了:
-(void)test
{
self.aBlock = ^{
[self someMethod];
};
}
现在让我们分解你的代码,以确保我们谈论的是相同的东西:
__weak MyClass *weakSelf = self;
[apiClient fetchData:^(...) {
typeof(self) selfref = weakSelf; <---- outer block
dispatch_async(dispatch_get_main_queue(), ^{
}
});
按步骤进行:
- 我们声明 weakSelf,这样在外部块中使用它时,它不会将强引用存储到 self 中 - retainCount保持不变。
- 我们通过捕获它的本地强变量来确保 weakSelf 在外部块结束前可用(如果您想知道为什么会发生这种情况,请注意 self 既不是 weak 也不是 strong - 它是 unsafe_unretained)。该本地强变量将在外部作用域结束时取消引用。因此,在外部块的开头只会增加+1,并在结尾处减少-1 - 没有块外的任何更改。
- 内部块将捕获对本地强引用的强引用(+1),但内部块本身是在外部块中声明的(将其视为变量,或查看第一个“test”场景),因此它将在安全释放外部块的末尾被释放。释放内部块将导致安全释放它所持有的本地强引用的引用,因此-1 引用计数 - 再次没有块范围外的任何更改。
希望您能明白所有这些,并与 ObjC 块战斗得光荣!