总是通过弱引用的自我在ARC块?

我对Objective-C中的块用法有点困惑。我目前使用ARC,我有相当多的块在我的应用程序,目前总是引用self而不是它的弱引用。这可能是这些块保留self并阻止它被释放的原因吗?问题是,我应该总是在块中使用selfweak引用吗?

-(void)handleNewerData:(NSArray *)arr
{
ProcessOperation *operation =
[[ProcessOperation alloc] initWithDataToProcess:arr
completion:^(NSMutableArray *rows) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateFeed:arr rows:rows];
});
}];
[dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
NSMutableArray *dataArr;
NSMutableArray *rowHeightsArr;
void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{


if(self =[super init]){
dataArr = [NSMutableArray arrayWithArray:data];
rowHeightsArr = [NSMutableArray new];
callback = cb;
}
return self;
}


- (void)main {
@autoreleasepool {
...
callback(rowHeightsArr);
}
}
122515 次浏览

你不必总是使用弱引用。如果你的块没有被保留,而是被执行然后被丢弃,你可以强捕获self,因为它不会创建一个保留周期。在某些情况下,你甚至希望block持有self直到block完成,这样它就不会过早释放。但是,如果您强力捕获块,并且在capture self内部,它将创建一个保留循环。

正如Leo所指出的,您添加到问题中的代码并不建议使用强引用循环(也称为保留循环)。一个可能导致强引用循环的与操作相关的问题是,如果没有释放操作。虽然你的代码片段表明你还没有将你的操作定义为并发的,但如果你已经定义了,如果你从未发布isFinished,或者如果你有循环依赖,或者类似的东西,它就不会被释放。如果操作没有被释放,视图控制器也不会被释放。我建议在操作的dealloc方法中添加一个断点或NSLog,并确认它正在被调用。

你说:

我理解保留周期的概念,但我不太确定在块中会发生什么,所以这让我有点困惑

块发生的保留周期(强引用周期)问题与您熟悉的保留周期问题一样。块将保持对出现在块内的任何对象的强引用,并且直到块本身被释放才会释放这些强引用。因此,如果块引用了self,或者只是引用了self的实例变量,这将保持对self的强引用,直到块被释放(或者在这种情况下,直到NSOperation子类被释放)才会被解析。

有关更多信息,请参阅使用Objective-C编程:使用块文档的捕获自我时避免强引用循环部分。

如果你的视图控制器仍然没有被释放,你只需要确定未解决的强引用驻留在哪里(假设你确认了NSOperation正在被释放)。一个常见的例子是使用重复的NSTimer。或者某个自定义delegate或其他错误地维护strong引用的对象。你可以经常使用工具来追踪对象在哪里获得它们的强引用,例如:

record reference counts in Xcode 6

或者在Xcode 5中:

record reference counts in Xcode 5

它有助于不关注讨论的strongweak部分。相反,关注周期部分。

周期是Object A保留Object B时发生的循环,而且 Object B保留Object A时发生的循环。在这种情况下,如果任意一个对象被释放:

  • 对象A不会被释放,因为对象B持有对它的引用。
  • 但是只要对象A有对对象B的引用,对象B就永远不会被释放。
  • 但是对象A永远不会被释放,因为对象B持有对它的引用。
  • 无限

因此,这两个对象将在程序的整个生命周期内一直驻留在内存中,即使它们应该(如果一切正常)被释放。

因此,我们所担心的是retain 周期,并且block本身并没有创建这些循环。这不是问题,例如:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
[self doSomethingWithObject:obj];
}];

块保留self,但self不保留块。如果一个或另一个被释放,就不会创建任何循环,所有的东西都应该被释放。

你遇到麻烦的地方是这样的:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);


//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self doSomethingWithObj:obj];
}];

现在,你的对象(self)有一个显式的strong引用。并且该块具有隐式的self的强引用。这是一个循环,现在这两个对象都将被正确地释放。

因为,在这种情况下,self 通过定义已经有一个对块的strong引用,通常最容易解决的方法是显式地对块使用的self进行弱引用:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[weakSelf doSomethingWithObj:obj];
}];

当处理调用self!这应该只用于打破原本是self和块之间的保留循环。如果你要在所有地方都采用这种模式,你将冒着将一个块传递给self被释放后才执行的东西的风险。

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
//By the time this gets called, "weakSelf" might be nil because it's not retained!
[weakSelf doSomething];
}];

一些解释忽略了保留循环的条件[如果一组对象由一个强关系的圈连接,即使没有来自组外的强引用,它们也会使彼此存活。要了解更多信息,请阅读文档

我完全同意@jemmons的观点:

但这不应该是处理调用self的块时遵循的默认模式!这应该只用于打破原本是self和块之间的保留循环。如果在所有地方都采用这种模式,就会冒着将一个块传递给某个在self被释放后才执行的对象的风险。

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
//By the time this gets called, "weakSelf" might be nil because it's not  retained!
[weakSelf doSomething];
}];

为了克服这个问题,可以在块内部的weakSelf上定义一个强引用:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
MyObject *strongSelf = weakSelf;
[strongSelf doSomething];
}];

下面是如何在block中使用self:

//调用块

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
[obj MethodNameYouWantToCall]; // this is how it will call the object
return @"Called";




};