为啥我需要为某些完成块而不是其他块创建自己的线程安全版本?

Posted

技术标签:

【中文标题】为啥我需要为某些完成块而不是其他块创建自己的线程安全版本?【英文标题】:Why do I need to create a threadsafe version of self for some completion blocks and not others?为什么我需要为某些完成块而不是其他块创建自己的线程安全版本? 【发布时间】:2015-01-09 19:24:09 【问题描述】:

我有一个带有 UIPageViewController 的视图,并且在设置视图控制器的方法上我使用了一个线程安全的 self 实例,如下所示:

__block typeof(self) threadedSelf = self;

[self.pageController setViewControllers:@[p]
                              direction:UIPageViewControllerNavigationDirectionReverse
                               animated:YES
                             completion:^(BOOL finished)
                                 if (finished) 
                                     [threadedSelf performSelectorOnMainThread:@selector(setNavTitleText) withObject:nil waitUntilDone:NO];
                                 
                             ];

完成块看起来与其他完成块相似,例如 UIView 动画方法,但这是我必须在其中创建 self.block 版本的第一个完成块。为什么此方法与其他完成回调不同?是不是因为这是一个实例方法,而 UIView 动画是一个类(静态)方法?

【问题讨论】:

你能解释一下为什么你认为你首先需要threadedSelf吗?您在这里做出初步假设;为什么?是因为setViewControllers 在后台线程上被调用吗?如果是这样,你为什么要这样做那个 如果我不使用块设置它,我会收到警告:“在此块中强烈捕获 'self' 可能会导致保留周期”。我确信我可以用 __weak 而不是 __block 做同样的事情,但我在 UIPageViewController 上看到的所有关于此方法的示例都专门使用 __block。 但这与“线程化”无关。它与内存管理有关。这正是我要问的。问题是你不明白什么是弱引用——为什么在这里需要一个,以及如何获得一个? 啊,明白了。我从一个教程中得到了一些,他们在解释中将变量列为“线程安全”。我想事实并非如此。我知道什么是弱引用,但我不确定 __block 引用或者为什么这个引用的管理方式必须与其他完成块不同。 弱引用是线程安全的,如果你做弱强的舞蹈,我在我对我的回答的评论中指出了这一点。原因是如果self 从你身下消失,weak-strong dance 可以让你安全地检测到产生的 nil weak ref 并且不再继续。我从未见过__block 以这种方式使用,这也不是 Apple 在其 WWDC 视频中官方推荐的;他们跳弱强舞蹈(他们甚至这么称呼它;我相信他们是从我那里得到这个名字的,但我可能错了,也许它在我开始使用它之前就已经存在了:) m. 【参考方案1】:

这并不是真正的线程安全问题,而是保留周期问题。块保留它们使用的对象。显然,从警告中可以看出,这个块被self 保留,所以如果它使用/保留self,那么你有一个保留周期。

您应该在块中使用的指向 self 的指针上使用 __weak 限定符,例如:

__weak typeof(self) weakSelf = self;

__weak__block 都经常用于将出现在块中的变量,但它们有不同的用途。 __weak 限定符可防止块保留对象,并且正是防止保留循环所需的工具。 __block 限定符确实阻止了块保留对象,但它的目的实际上是表明对象可能在块内被更改(即对块内对象所做的更改必须在块外可见)。在您的示例中,不是。您引用了self,但您没有修改指针 (self = foo)。因此,您应该使用__weak 限定符而不是__block 限定符。

在许多情况下,您都不需要限定符。只有当块实际上被块内引用的对象保留时,才会有一个保留周期。通常,您会使用不被您拥有的任何对象保留的块,例如:

[UIView animateWithDuration:0.2 animations:^
    [self makeSomeChangesToBeAnimated];
];

这里的动画块可以安全地引用self而没有限定符,因为self没有保留该块。 else 可能会保留它,但self 不会保留它,因此您不需要使用__weak 限定符。

【讨论】:

完美!这正是我一直在寻找的。谢谢亚伦!【参考方案2】:

如果self 保留一个引用self 的块,我们就有一个保留循环。您试图避免的警告是告诉您这可能会发生在这里。我不知道这是否属实,但鉴于明确的警告,我不会冒险:我使用“弱强舞蹈”将self 的弱版本传递到块中。

【讨论】:

关于弱强之舞的结构以及关于内存管理和保留周期的进一步解释,请参阅我的讨论:apeth.com/iosBook/ch12.html#EXstrongWeakDance 所以这与其他完成块不同,因为它是一个实例方法,它在自身实例上调用该方法(可能在完成之前消失),其中类似于 UIView 动画完成处理程序是一个类方法完成前哪些不能发布?

以上是关于为啥我需要为某些完成块而不是其他块创建自己的线程安全版本?的主要内容,如果未能解决你的问题,请参考以下文章

MVC:面包块而不是面条|精选博客

调用 AFNetworking 响应失败块而不是成功块

隐藏块而不是删除

什么是线程安全,实现线程安全都有哪些方法

Java并发3

IOCP 是不是创建自己的线程?