假设,self是一个指向 a 的对象指针UIViewController
。
需要考虑的事项:
AUIViewController
是一个“UIKit”对象。UIKit 对象不应在非主线程上发送方法,即 - 这些方法必须仅在主线程上执行!
已排入队列的块——无论是同步的还是异步的——最终都会被执行——无论如何!好吧,除非程序在这发生之前终止。
当块被复制时(例如,异步调度时),捕获的可保留强指针将被保留,并在块被销毁时(完成后)再次释放。
捕获的可保留弱指针不会被保留也不会被释放。
在您的场景中,您在主队列上调度的块中捕获self,您不必担心会发生坏事。
所以为什么?实际上会发生什么?
由于self将在异步调度的块中被捕获,因此self将被隐式保留,并在块完成后再次释放。
这意味着,self的生命周期将延长到块完成之后。请注意,您的第二个块是在主线程上调度的,并且可以保证当该块被执行时self仍然存在。
上面的这种“延长寿命”可能是您程序的理想功能。
如果您明确不想延长UIViewController
对象的生命周期,而是想要块 - 当它最终执行时 -检查该UIViewController
对象是否仍然存在,您可以使用 self 的 __weak 指针。请注意,该块最终会被执行,无论它是否UIViewController
仍然存在或同时已被释放。
如果在块将被执行之前UIViewController
已被释放,您可能希望块“什么都不做” :
MyController* __weak weakSelf = self;
dispatch_async(queue, ^{
MyController* strongSelf = weakSelf;
if (strongSelf) {
...
}
else {
// self has been deallocated in the meantime.
}
});
另请参阅:过渡到 ARC 发行说明
请记住:UIKit
对象不应在非主线程上发送方法!
UIKit
由于对象只能在主线程上执行方法这一事实,可能会发生另一个微妙的错误。
如果一个块捕获了一个UIKit
异步分派的对象,并在非主线程上执行,这可能会被违反。然后可能会发生该块持有对该对象的最后一个强引用UIKit
。现在,当块最终被执行时,块将被销毁,UIKit
对象将被释放。由于这是对该对象的最后一个强引用UIKit
,它将被释放。但是,这发生在执行该块的线程上 - 这不是主线程!现在,坏事可能(并且通常会)发生,因为dealloc
方法仍然是发送给UIKit
对象的方法。
您可以通过调度一个捕获指向该 UIKit 对象的强指针的块,并向其发送一个虚拟方法来避免此错误:
UIViewController* strongUIKitPointer = ...
dispatch_async(non_main_queue, ^{
... // do something
dispatch(dispatch_get_main_queue(), ^{
[strongUIKitPointer self]; // note: self is a method, too - doing nothing
});
});
但是,在您的场景中,最后一个强引用可能仅在主线程上执行的块中。因此,您可以避免这个微妙的错误。;)
编辑:
在您的设置中,您永远不会有保留周期。如果可保留对象 A 强引用另一个可保留对象 B,而对象 B 强引用 A,则会发生保留循环。请注意,“块”也是可保留对象。
一个带有循环引用的人为示例:
typedef void(^my_completion_block_t)(NSArray* result);
@interface UsersViewController : UIViewController
@property (nonatomic, copy) my_completion_block_t completion;
@property (nonatomic) NSArray* users;
@end
在这里,我们有一个属性完成,其值类型是块。也就是说,我们得到一个名称_completion
为 Block 的 ivar。
客户端可以设置一个完成处理程序,当某个操作完成时应该调用它。假设,该操作从远程服务器获取用户列表。计划是在操作完成后设置属性用户:
粗心的方法会不小心引入循环引用:
“UsersViewController.m”中的某处
self.completion = ^(NSArray* users){
self.users = users;
}
[self fetchUsers]; // start asynchronous task
在这里,self持有对 ivar 的强引用_completion
,它是一个块。并且块本身会捕获self,这会导致在调度块被复制时保留self 。这是一个经典的参考循环。
为了避免这种循环引用,我们有一些选择:
使用self__weak
的限定指针
UsersViewController* __weak weakSelf = self;
self.completion = ^(NSArray* users) {
UsersViewController* strongSelf = weakSelf;
if (strongSelf) {
strongSelf.users = users;
}
else {
// the view controller does not exist anymore
}
}
[usersViewController fetchUsers];
使用self__block
的限定指针并最终在完成时将其设置在块中:nil
UsersViewController* __block blockSelf = self;
self.completion = ^(NSArray* users) {
blockSelf.users = users;
blockSelf = nil;
}
[usersViewController fetchUsers];
另请参阅:过渡到 ARC 发行说明