在dispatch_async函数中使用weak self

61

我看了很多关于在dispatch_async里使用__weak self的帖子,现在有点困惑。

如果我有:

self.myQueue = dispatch_queue_create("com.biview.core_data", NULL);

dispatch_async(self.myQueue, ^(void){
    if (!self.var1) {
        self.var1 = ...;
    }
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        if ([self.var2 superview]) {
            [self.var2 removeFromSuperview];
        }

        [self.Label setText:text];
    });
});

我需要使用__weak self吗?因为我读到在某些情况下,dispatch_async不需要__weak self

看这里的最后一条评论

2个回答

148

假设 self 是一个指向 UIViewController 的对象指针。

需要考虑的事情:

  • UIViewController 是一个“UIKit”对象。UIKit 对象不应该在非主线程上发送方法,也就是说 - 这些方法必须只能在主线程上执行!

  • 已经被加入队列(无论是同步还是异步)的块最终都将被执行,除非程序在此之前终止。

  • 当拷贝块(例如,异步调度时)时,捕获的可保留的 strong 指针将被保留,并且在块销毁后(完成后)再次释放。

  • 捕获的可保留的 weak 指针将不会被保留或释放。

在您的场景中,您在分派到 主队列 上的块中捕获了 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,则会发生保留循环。 请注意,“Block”也是可保留对象。

一个有循环引用的人为例子:

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。也就是说,我们会得到一个名为_completion且类型为Block的ivar。

客户端可以设置完成处理程序,当某个操作完成时应该调用该处理程序。假设,该操作从远程服务器获取用户列表。计划是在操作完成后设置users属性:

粗心大意的方法会意外地引入一个循环引用:

在“UsersViewController.m”中的某个地方

self.completion = ^(NSArray* users){
    self.users = users;
}

[self fetchUsers];  // start asynchronous task

在这里,self 持有一个指向 _completion 的强引用,这是一个块对象。而且该块对象捕获 self,当块对象被复制并分发时,就会导致 self 被保留。这是一个典型的引用循环。

为了避免这种循环引用,我们有几个选择:

  1. 使用 __weak 限定符修饰的指针来引用 self

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];
使用带有__block修饰符的self指针,并在块完成时将其设置为nil:
UsersViewController* __block blockSelf = self;
self.completion = ^(NSArray* users) {
    blockSelf.users = users;
    blockSelf = nil;
}   
[usersViewController fetchUsers];

相关链接: 转换到ARC发布说明


哇,这个回答非常有用,谢谢。所以如果我理解正确,在调用dispatch_async之后,即使是一个虚拟函数在主线程上执行,它也可以防止出现保留循环,并且我不需要使用__weak引用来避免? - Marko Zadravec
你能否举个例子给我吗?我觉得我理解了,但我想确认一下。 - Marko Zadravec
@MarkoZadravec 好的,我添加了一个导致保留循环的示例和预防此问题的解决方案。 - CouchDeveloper
哇,太棒了。所以,例如,如果我使用onelly 'dispatch_async(self.coreDataQueue,^(void){...}',它不会进入保留循环,因为此块没有强iVars来保留'self'。 - Marko Zadravec
@MarkoZadravec 是的,至少没有涉及_self_的保留循环。由于self不会引用该块,因此该块可以捕获_self_ -- 而不会引入保留循环。尽管如此,请注意:在更微妙和不明显的情况下,可能会发生保留循环。 - CouchDeveloper
显示剩余9条评论

31

Swift 更新:

这是 Swift 中所谓的强弱引用操作的示例:

Swift 4.2:

func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let self = self else { return }
            self.updateView()
        }
    }
}

Swift 3 & 4:


(翻译:Swift 3和4)
func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let strongSelf = self else { return }
            strongSelf.updateView()
        }
    }
}

Swift 2:

->

Swift 2:

func doSomeThingAsynchronously() {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> () in
        // Do task in default queue
        dispatch_async(dispatch_get_main_queue(), { [weak self] () -> () in
            guard let strongSelf = self else { return }
            // Do task in main queue
            strongSelf.updateView()
        })
    }
}

流行的开源项目Alamofire使用这种方法。

使用[weak self]guard let strongSelf = self else { return }来延长对象生命周期。

欲了解更多信息,请查看swift-style-guide


11
有趣的事实:在Swift中,您可以通过使用guard let `self` = self else { return }来避免使用strongSelf语法。 - Chase Holland
5
“self”的使用实际上是编译器的一个错误,未来的Swift版本可能会移除它。请查看Swift的Chris Lattner在此处的完整解释:https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/007425.html - John Rogers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接