保持循环“自我”与块

恐怕这个问题非常基本,但我认为它与许多正在进入块的 Objective-C 程序员有关。

我所听到的是,由于块捕获它们中作为 const副本引用的本地变量,因此在块中使用 self可能导致保留循环,如果该块被复制的话。因此,我们应该使用 __block来强制块直接处理 self,而不是复制它。

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

而不是仅仅

[someObject messageWithBlock:^{ [self doSomething]; }];

我想知道的是: 如果这是真的,有没有办法可以避免丑陋(除了使用 GC) ?

59122 次浏览

严格地说,它是一个常量副本的事实与这个问题没有任何关系。块将保留在创建时捕获的任何 obj-c 值。碰巧常量拷贝问题的解决方案与保留问题的解决方案相同; 即,对变量使用 __block存储类。

无论如何,回答你的问题,这里没有其他选择。如果您正在设计自己的基于块的 API,并且这样做是有意义的,那么您可以将 self的值作为参数传递给块。不幸的是,这对大多数 API 来说没有意义。

请注意,引用一个 ivar 具有完全相同的问题。如果需要在块中引用一个 ivar,可以使用一个属性,也可以使用 bself->ivar


附录: 当编译为 ARC 时,__block不再中断保留周期。如果要为 ARC 编译,则需要使用 __weak__unsafe_unretained

这可能是显而易见的,但是只有当你知道你会得到一个保留循环时,你才需要使用丑陋的 self别名。如果块只是一次性的事情,那么我认为你可以安全地忽略保留 self。例如,坏的情况是将块作为回调接口。比如这里:

typedef void (^BufferCallback)(FullBuffer* buffer);


@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end


@implementation AudioProcessor


- (id) init {
…
[self setBufferCallback:^(FullBuffer* buffer) {
[self whatever];
}];
…
}

在这里,API 没有多大意义,但是在与超类通信时,它是有意义的,例如。我们保留缓冲区处理程序,缓冲区处理程序保留我们。比较一下:

typedef void (^Callback)(void);


@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end


@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end


@implementation Foo
- (void) somewhere {
[encoder encodeVideoAndCall:^{
[self doSomething];
}];
}

在这些情况下,我不做 self别名。您确实得到了一个保留循环,但是操作是短暂的,块最终将从内存中脱离,从而打破了这个循环。但我的经验与块是非常小的,它可能是 self别名出来作为一个最佳实践在长期。

还要记住,如果块引用 另一个对象,那么保留循环可能会发生,另一个对象随后保留 self

我不确定垃圾收集可以帮助这些保留周期。如果保留块的对象(我称之为服务器对象)比 self(客户端对象)存在时间长,那么在释放保留对象本身之前,不会认为块内对 self的引用是循环的。如果服务器对象的寿命远远超过其客户机,则可能存在严重的内存泄漏。

由于没有干净的解决方案,我建议采用以下变通方法。你可以自由选择其中的一个或多个来解决你的问题。

  • 只对 完成使用块,不要对开放式事件使用块。例如,对类似 doSomethingAndWhenDoneExecuteThisBlock:的方法使用块,而不是对类似 setNotificationHandlerBlock:的方法使用块。用于完成的块具有明确的生命周期结束,并且应该在对它们进行计算之后由服务器对象释放。这可以防止保留周期的生活太长,即使它发生。
  • 跳你说的那种弱参照舞。
  • 提供一个方法来在对象释放之前清理它,从而“断开”对象与可能保存对它的引用的服务器对象的连接; 并在对对象调用 release 之前调用此方法。如果您的对象只有一个客户端(或者在某些上下文中是一个单例) ,那么这个方法完全没问题,但是如果它有多个客户端,那么它就会崩溃。这里基本上破坏了保留计数机制; 这类似于调用 dealloc而不是 release

如果您正在编写服务器对象,那么只有在完成时才使用块参数。不要接受回调的块参数,例如 setEventHandlerBlock:。相反,回到传统的委托模式: 创建一个正式的协议,并公布一个 setEventDelegate:方法。不要保留代表。如果您甚至不想创建正式的协议,那么接受选择器作为委托回调。

最后,这种模式应该会敲响警钟:

- (void)dealloc {
[myServerObject releaseCallbackBlocksForObject:self];
...
}

如果您试图从 dealloc内部解除可能引用 self的挂钩块,那么您已经有麻烦了。由于块中引用引起的保留周期,可能永远不会调用 dealloc,这意味着您的对象将会泄漏,直到服务器对象被释放。

发布另一个答案,因为这对我来说也是一个问题。我最初认为我必须在块中任何有自我引用的地方使用 block Self。事实并非如此,只有当对象本身包含一个块时才会出现这种情况。实际上,如果在这些情况下使用 lockSelf,对象可以在从块返回结果之前被释放,然后在尝试调用它时会崩溃,所以很明显,您希望 self 被保留,直到响应返回。

第一种情况说明保留循环何时发生,因为它包含一个块,该块中引用了:

#import <Foundation/Foundation.h>


typedef void (^MyBlock)(void);


@interface ContainsBlock : NSObject


@property (nonatomic, copy) MyBlock block;


- (void)callblock;


@end


@implementation ContainsBlock
@synthesize block = _block;


- (id)init {
if ((self = [super init])) {


//__block ContainsBlock *blockSelf = self; // to fix use this.
self.block = ^{
NSLog(@"object is %@", self); // self retain cycle
};
}
return self;
}


- (void)dealloc {
self.block = nil;
NSLog (@"ContainsBlock"); // never called.
[super dealloc];
}


- (void)callblock {
self.block();
}


@end


int main() {
ContainsBlock *leaks = [[ContainsBlock alloc] init];
[leaks callblock];
[leaks release];
}

在第二种情况下,您不需要 lockSelf,因为调用对象中没有块,当您引用 self 时,它会导致一个保留循环:

#import <Foundation/Foundation.h>


typedef void (^MyBlock)(void);


@interface BlockCallingObject : NSObject
@property (copy, nonatomic) MyBlock block;
@end


@implementation BlockCallingObject
@synthesize block = _block;


- (void)dealloc {
self.block = nil;
NSLog(@"BlockCallingObject dealloc");
[super dealloc];
}


- (void)callblock {
self.block();
}
@end


@interface ObjectCallingBlockCallingObject : NSObject
@end


@implementation ObjectCallingBlockCallingObject


- (void)doneblock {
NSLog(@"block call complete");
}


- (void)dealloc {
NSLog(@"ObjectCallingBlockCallingObject dealloc");
[super dealloc];
}


- (id)init {
if ((self = [super init])) {


BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
myobj.block = ^() {
[self doneblock]; // block in different object than this object, no retain cycle
};
[myobj callblock];
[myobj release];
}
return self;
}
@end


int main() {


ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
[myObj release];


return 0;
}

这个怎么样?

- (void) foo {
__weak __block me = self;


myBlock = ^ {
[[me someProp] someMessage];
}
...
}

我不再收到编译器的警告了。

使用:

__weak id weakSelf = self;


[someObject someMethodWithBlock:^{
[weakSelf someOtherMethod];
}];

更多信息: WWDC 2011-实践中的街区和中央车站调度

Https://developer.apple.com/videos/wwdc/2011/?id=308

注意: 如果这不起作用,你可以试试

__weak typeof(self)weakSelf = self;

块: 一个保留循环将发生,因为它包含一个块,是在块中引用; 如果使用块复制并使用成员变量,self 将保留。

Kevin 的帖子中建议的 __block __unsafe_unretained修饰符可能会导致在不同线程中执行块时出现错误的访问异常。对于 temp 变量,最好只使用 阻止他们修饰符,并在使用后将其设置为 nil。

__block SomeType* this = self;
[someObject messageWithBlock:^{
[this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
//  multithreading and self was already released
this = nil;
}];

您可以使用 libextobjc 库,它非常流行,例如在 ReactiveCocoa 中使用。 Https://github.com/jspahrsummers/libextobjc

它提供了两个宏:

@weakify(self)
[someObject messageWithBlock:^{
@strongify(self)
[self doSomething];
}];

这样可以防止直接的强引用,所以我们不会陷入自我保留的循环。同时,它可以防止自身在中途变为零,但仍然适当地减少保留计数。 更多相关资料: Http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html