我很难将一些NSOperation代码转换为ARC。我的操作对象使用完成块,该完成块又包含一个GCD块,用于更新主线程上的UI。因为我从自己的完成块中引用了我的操作对象,所以我使用__weak指针来避免内存泄漏。但是,在我的代码运行时,指针已经设置为nil。
我已将其缩小到此代码示例。谁知道我哪里出错了,以及正确的方法来实现这个目标?
NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__weak NSOperationSubclass *weakOperation = operation;
[operation setCompletionBlock:^{
dispatch_async( dispatch_get_main_queue(), ^{
// fails the check
NSAssert( weakOperation != nil, @"pointer is nil" );
...
});
}];
答案 0 :(得分:16)
另一种选择是:
NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__weak NSOperationSubclass *weakOperation = operation;
[operation setCompletionBlock:^{
NSOperationSubclass *strongOperation = weakOperation;
dispatch_async(dispatch_get_main_queue(), ^{
assert(strongOperation != nil);
...
});
}];
[operationQueue addOperation:operation];
我假设您还将操作对象添加到NSOperationQueue。在这种情况下,队列保留操作。它可能在执行完成块期间保留它(尽管我还没有找到关于完成块的官方确认)。
但是在完成块内部会创建另一个块。该块将在稍后的某个时间点运行,可能在NSOperations的完成块运行结束之后。发生这种情况时,队列将释放operation
,weakOperation
将nil
。operation
。但是如果我们从操作的完成块创建另一个对同一对象的强引用,我们将确保在运行第二个块时存在operation
,并避免保留周期,因为我们没有捕获{{1}}由块改变。
Apple在Transitioning to ARC Release Notes中提供此示例,请参阅使用终身限定符以避免强引用周期部分中的最后一个代码段。
答案 1 :(得分:10)
我不确定这一点,但正确的方法是将__block添加到有问题的变量中,然后在块的末尾将其设置为nil以确保它被释放。 See this question.
您的新代码如下所示:
NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__block NSOperationSubclass *weakOperation = operation;
[operation setCompletionBlock:^{
dispatch_async( dispatch_get_main_queue(), ^{
// fails the check
NSAssert( weakOperation != nil, @"pointer is nil" );
...
weakOperation = nil;
});
}];
答案 2 :(得分:4)
接受的答案是正确的。但是,从iOS 8 / Mac OS 10.10开始,无需弱化操作:
NSOperation documentation on @completionBlock的引用:
在iOS 8及更高版本和OS X v10.10及更高版本中,在完成块开始执行后,此属性设置为nil。
另见Pete Steinberger的this tweet。