在dispatch_async函数中使用弱自我

时间:2014-02-24 11:55:27

标签: ios objective-c-blocks weak-references retain-cycle

我在__weak self内阅读了很多关于使用dispatch_async的帖子,现在我对此感到困惑。

如果我有:

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

See last comment here

2 个答案:

答案 0 :(得分:132)

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

需要考虑的事项:

  • UIViewController是“UIKit”对象。 UIKit对象不应该在非主线程上发送方法,即 - 那些方法只能在主线程上执行!

  • 已排入队列的块 - 无论是同步还是异步 - 最终将执行 - 无论如何!好吧,除非程序在此之前终止。

  • 当复制块时(例如,异步调度时),已释放保留 em>当块被销毁时(完成后)。

  • 捕获的可保留指针不会被保留而不会被释放。

在您的方案中,您在主队列中调度的块中捕获 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.
    }
});

另请参阅:Transitioning to ARC Release Notes

请记住:UIKit对象不应该在非主线程上发送方法!

由于UIKit个对象只能在主线程上执行方法,因此可能会发生另一个细微的错误。

如果一个块捕获异步调度的UIKit对象,并在非主线程上执行,则可能违反此规则。然后可能会发生该块保存 last 对该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
    });
});

在您的方案中, last strong 引用只能在主线程上执行的块中。因此,您可以避免这种微妙的错误。 ;)

编辑:

在您的设置中,您永远不会有保留周期。如果可保留对象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。也就是说,我们得到一个名为_completion的ivar,其类型为Block。

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

粗心的方法会不小心引入循环引用:

“UsersViewController.m”中的某个地方

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

[self fetchUsers];  // start asynchronous task

在这里, self 拥有对ivar _completion的强引用,这是一个块。块本身捕获 self ,这会导致在调度块被复制时保留 self 。这是一个经典的参考周期。

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

  1. 使用 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];
    
  2. 使用 self __block限定指针,并在完成后最终在块中设置nil

    UsersViewController* __block blockSelf = self;
    self.completion = ^(NSArray* users) {
        blockSelf.users = users;
        blockSelf = nil;
    }   
    [usersViewController fetchUsers];
    
  3. 另请参阅:Transitioning to ARC Release Notes

答案 1 :(得分:26)

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:

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()
        }
    }
}

斯威夫特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 使用此方法。

  

使用 [弱自我] 后卫 strongSelf = self else {return}成语延长对象生命周期。

有关详细信息,请查看swift-style-guide