使用块保留`self`循环

Jon*_*ing 166 memory-management objective-c objective-c-blocks

我担心这个问题非常基本,但我认为这与很多进入数据块的Objective-C程序员有关.

我听到的是,由于块捕获作为const副本在其中引用的局部变量self,因此如果要复制该块,则在块内使用可能会导致保留周期.因此,我们应该使用__block强制块直接处理self而不是复制它.

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
Run Code Online (Sandbox Code Playgroud)

而不仅仅是

[someObject messageWithBlock:^{ [self doSomething]; }];
Run Code Online (Sandbox Code Playgroud)

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

Lil*_*ard 168

严格来说,它是一个const副本的事实与这个问题无关.块将保留创建时捕获的任何obj-c值.恰好,const-copy问题的解决方法与保留问题的解决方法相同; 即,使用__block变量的存储类.

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

请注意,引用ivar具有完全相同的问题.如果您需要在块中引用ivar,请使用属性或使用bself->ivar.


附录:编译为ARC时,__block不再中断保留周期.如果您正在为ARC编译,则需要使用__weak__unsafe_unretained替代.

  • 没问题,凯文.因此,您不会立即选择问题的答案,所以我不得不稍后回来.干杯. (4认同)
  • @JKLaiho:当然,`__weak`也很好.如果你知道在调用块时对象不能超出范围这一事实,那么`__unsafe_unretained`的速度会稍微快一些,但总的来说这不会产生任何影响.如果你确实使用`__weak`,请确保将它放入一个`__strong`局部变量,并在使用它之前测试非``nil`. (4认同)
  • @Rpranata:是的.`__block`的不保留和释放的副作用纯粹是由于无法正确推理.使用ARC,编译器获得了这种能力,因此`__block`现在保留并释放.如果需要避免这种情况,则需要使用`__unsafe_unretained`,它指示编译器不对变量中的值执行任何保留或释放. (2认同)

3lv*_*vis 65

只需使用:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];
Run Code Online (Sandbox Code Playgroud)

欲了解更多信息:WWDC 2011 - Block and Grand Central Dispatch in Practice.

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

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

__weak typeof(self)weakSelf = self;
Run Code Online (Sandbox Code Playgroud)

  • 你有没有机会找到它:)? (2认同)
  • 您可以在此处查看视频 - https://developer.apple.com/videos/wwdc/2011/#blocks-and-grand-central-dispatch-in-practice (2认同)

zou*_*oul 22

这可能是显而易见的,但是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];
    }];
    …
}
Run Code Online (Sandbox Code Playgroud)

这里的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];
    }];
}
Run Code Online (Sandbox Code Playgroud)

在这些情况下,我不做self别名.你确实得到一个保留周期,但是操作是短暂的,并且块最终会从内存中断开,从而打破周期.但是我对块的体验非常小self,从长远来看,混叠可能是最佳实践.

  • 原则上,你是对的.但是,如果您要执行示例中的代码,则会崩溃.块属性应*总是*声明为`copy`,而不是`retain`.如果它们只是"保留",那么就不能保证它们会被移出堆栈,这意味着当你去执行它时,它就不再存在了.(并且复制和已复制的块已优化为保留) (6认同)
  • 好点子.如果自我保持块活着,那只是一个保留周期.对于永远不会被复制的块,或具有保证有限持续时间的块(例如UIView动画的完成块),您不必担心它. (5认同)

pos*_*sen 19

发布另一个答案,因为这对我来说也是一个问题.我原本以为我必须在块内部有自引用的任何地方使用blockSelf.情况并非如此,只有当对象本身有一个块时才会出现.事实上,如果你在这些情况下使用blockSelf,那么在你从块中获得结果之前,对象可以被释放,然后当它试图调用它时它会崩溃,所以很明显你希望自己被保留直到响应回来.

第一种情况说明何时会发生保留周期,因为它包含块中引用的块:

#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];
}
Run Code Online (Sandbox Code Playgroud)

在第二种情况下你不需要blockSelf,因为调用对象中没有一个块,当你引用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;
} 
Run Code Online (Sandbox Code Playgroud)


Dav*_*e R 9

还要记住,如果块引用另一个随后保留的对象,则可能会发生保留周期self.

我不确定垃圾收集可以帮助这些保留周期.如果保留块的对象(我将称之为服务器对象)超出self(客户端对象),则self在释放保留对象本身之前,对块内部的引用将不会被视为循环.如果服务器对象远远超过其客户端,则可能会发生严重的内存泄漏.

由于没有干净的解决方案,我建议采用以下解决方法.随意选择其中一个或多个来解决您的问题.

  • 仅使用块来完成,而不是用于开放式事件.例如,使用块作为方法doSomethingAndWhenDoneExecuteThisBlock:,而不是类似的方法setNotificationHandlerBlock:.用于完成的块具有明确的生命终结,并且应在评估后由服务器对象释放.这可以防止保留周期长时间存活,即使它发生.
  • 做你描述的弱参考舞蹈.
  • 提供一种在对象释放之前清理对象的方法,该对象将对象与可能包含对象的服务器对象"断开连接"; 并在调用对象上的release之前调用此方法.虽然如果您的对象只有一个客户端(或者在某些上下文中是单例),但这种方法非常好,但如果它有多个客户端则会崩溃.你基本上打败了保留计数机制; 这类似于打电话dealloc而不是release.

如果要编写服务器对象,请仅使用块参数完成.不接受回调的块参数,例如setEventHandlerBlock:.相反,回到经典的委托模式:创建一个正式的协议,并宣传一个setEventDelegate:方法.不要保留代表.如果您甚至不想创建正式协议,请接受选择器作为委托回调.

最后,这种模式应响铃:

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

如果您试图解开可能self从内部引用的块dealloc,那么您已经遇到了麻烦.dealloc由于块中的引用导致的保留周期,可能永远不会被调用,这意味着您的对象只是泄漏,直到服务器对象被释放.