总是将 self 的弱引用传递到 ARC 中的块中?
Posted
技术标签:
【中文标题】总是将 self 的弱引用传递到 ARC 中的块中?【英文标题】:Always pass weak reference of self into block in ARC? 【发布时间】:2013-11-30 14:31:00 【问题描述】:我对 Objective-C 中的块使用有点困惑。我目前使用 ARC,我的应用程序中有很多块,目前总是引用 self
而不是它的弱引用。这可能是这些块保留self
并防止它被释放的原因吗?问题是,我是否应该始终在块中使用self
的weak
引用?
-(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);
【问题讨论】:
如果你想深入讨论这个话题,请阅读dhoerl.wordpress.com/2013/04/23/… 【参考方案1】:不要专注于讨论中的strong
或weak
部分会有所帮助。而是专注于循环部分。
A retain cycle 是当对象 A 保留对象 B,并且对象 B 保留对象 A 时发生的循环。在这种情况下,如果任一对象被释放:
对象 A 不会被释放,因为对象 B 持有对它的引用。 但只要对象 A 有对它的引用,对象 B 就永远不会被释放。 但对象 A 永远不会被释放,因为对象 B 持有对它的引用。 无限因此,这两个对象将在程序的整个生命周期中一直在内存中徘徊,即使如果一切正常,它们应该被释放。
所以,我们担心的是保留循环,而创建这些循环的块本身并没有什么。这不是问题,例如:
[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
被释放后执行的东西的风险。
//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];
];
【讨论】:
我不确定 A 保留 B,B 保留 A 会做一个无限循环。从引用计数的角度来看,A 和 B 的引用计数为 1。导致这种情况的保留循环是当外部没有其他组对 A 和 B 具有强引用时——这意味着我们无法到达这两个对象(我们无法控制A释放B,反之亦然),因此A和B相互引用以保持自己的生命。 @Danyun 虽然在对这些对象的所有其他引用都被释放之前,A 和 B 之间的保留循环确实是不可恢复,但这并没有减少它一个循环。相反,仅仅因为一个特定的循环可能是可恢复的,并不意味着可以在你的代码中使用它。保留周期是糟糕设计的味道。 @jemmons 是的,我们应该尽可能避免保留循环设计。 @Master 我没法说。这完全取决于您的-setCompleteionBlockWithSuccess:failure:
方法的实现。但是如果paginator
归ViewController
所有,并且在ViewController
被释放后这些块不会被调用,那么使用__weak
引用将是安全的举措(因为self
拥有拥有块,因此当块调用它时可能仍然存在,即使它们不保留它)。但这是很多“如果”。这真的取决于这应该做什么。
@Jai 不,这是块/闭包的内存管理问题的核心。当没有人拥有它们时,对象会被释放。 MyObject
和 SomeOtherObject
都拥有该块。但是因为块对MyObject
的引用是weak
,所以块不拥有MyObject
。因此,虽然只要 either MyObject
or SomeOtherObject
存在,就保证该块存在,但不能保证只要该块存在,MyObject
就会存在. MyObject
可以完全解除分配,只要SomeOtherObject
仍然存在,该块仍然存在。【参考方案2】:
我完全同意@jemmons:
但这不应该是您在处理调用 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];
];
【讨论】:
strongSelf 不会增加weakSelf 的引用计数吗?从而创建一个保留周期? 保留周期仅在对象处于静态状态时才有意义。当代码正在执行并且它的状态在不断变化时,多个并且可能是冗余的保留是可以的。无论如何,关于这种模式,在块运行之前捕获一个强引用对于 self 被释放的情况没有任何作用,这种情况仍然可能发生。它确实确保 self 在执行块时不会被释放。如果块本身执行异步操作,这很重要,因为它提供了一个窗口来实现。 @smallduck,你的解释很好。现在我更好地理解了这一点。书籍未涵盖此内容,谢谢。 这不是 strongSelf 的一个很好的例子,因为 strongSelf 的显式添加正是运行时无论如何都会做的:在 doSomething 行上,在方法调用期间采用强引用.如果 weakSelf 已经失效,则强 ref 为 nil 并且方法调用是空操作。如果您有一系列操作或访问成员字段 (->
),strongSelf 有帮助的地方,您想保证您实际上获得了有效的引用并在整个操作集中持续保持它,例如if ( strongSelf ) /* several operations */
【参考方案3】:
您不必总是使用弱引用。如果你的块没有被保留,而是被执行然后被丢弃,你可以强捕获自我,因为它不会创建一个保留循环。在某些情况下,您甚至希望块保持自身直到块完成,这样它就不会过早地释放。但是,如果您强烈捕获块,并且在捕获自身内部,它将创建一个保留循环。
【讨论】:
好吧,我只是将块作为回调执行,我根本不希望 self 被释放。但似乎我正在创建保留周期,因为有问题的视图控制器没有被释放...... 例如,如果你有一个被视图控制器保留的条形按钮项,并且你在该块中强捕获自我,就会有一个保留周期。 @MartinE。您应该只使用代码示例更新您的问题。 Leo 非常正确(+1),它不一定会导致强引用循环,但它可能会导致,这取决于您如何使用这些块。如果您提供代码 sn-ps,我们将更容易为您提供帮助。 @LeoNatan 我理解保留周期的概念,但我不太确定块中会发生什么,所以这让我有点困惑 在上面的代码中,一旦操作完成并释放块,您的self实例将被释放。您应该阅读块的工作原理以及它们在其范围内捕获的内容和时间。【参考方案4】:正如 Leo 所指出的,您添加到问题中的代码不会暗示强引用循环(也称为保留循环)。一个可能导致强引用周期的与操作相关的问题是,如果操作没有被释放。虽然您的代码 sn-p 表明您尚未将操作定义为并发,但如果您有,如果您从未发布 isFinished
,或者如果您有循环依赖或类似的东西,它将不会被释放。如果操作没有被释放,视图控制器也不会被释放。我建议在您的操作的 dealloc
方法中添加断点或 NSLog
并确认它被调用。
你说:
我理解保留周期的概念,但我不太确定块中会发生什么,这让我有点困惑
块出现的保留周期(强引用周期)问题就像您熟悉的保留周期问题一样。块将保持对块内出现的任何对象的强引用,并且在块本身被释放之前它不会释放这些强引用。因此,如果块引用self
,甚至只是引用self
的实例变量,这将保持对自身的强引用,直到块被释放(或者在这种情况下,直到NSOperation
子类)才解决被释放了。
有关详细信息,请参阅Objective-C 编程:使用块文档的Avoid Strong Reference Cycles when Capturing self 部分。
如果您的视图控制器仍未释放,您只需确定未解析的强引用所在的位置(假设您确认 NSOperation
正在被释放)。一个常见的例子是使用重复的NSTimer
。或者一些自定义的delegate
或其他错误地维护strong
引用的对象。您通常可以使用 Instruments 来追踪对象从何处获得强引用,例如:
或者在 Xcode 5 中:
【讨论】:
另一个例子是,如果操作保留在块创建器中并且一旦完成就不会释放。 +1 写得好! @LeoNatan 同意,尽管代码 sn-p 将其表示为局部变量,如果他使用 ARC 将被释放。但你说的很对! 是的,我只是举个例子,正如 OP 在另一个答案中要求的那样。 顺便说一句,Xcode 8 有“Debug Memory Graph”,这是一种更简单的方法来查找对尚未释放的对象的强引用。见***.com/questions/30992338/…。【参考方案5】:一些解释忽略了关于保留循环的条件[如果一组对象通过一个强关系圈连接,即使没有来自组外的强引用,它们也会相互保持活动状态。]有关更多信息,请阅读document
【讨论】:
【参考方案6】:这是你可以在块内使用 self 的方法:
//块的调用
NSString *returnedText= checkIfOutsideMethodIsCalled(self);
NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
[obj MethodNameYouWantToCall]; // this is how it will call the object
return @"Called";
;
【讨论】:
以上是关于总是将 self 的弱引用传递到 ARC 中的块中?的主要内容,如果未能解决你的问题,请参考以下文章