简体   繁体   English

使用块保留`self`循环

[英]Retain cycle on `self` with blocks

I'm afraid this question is pretty basic, but I think it's relevant to a lot of Objective-C programmers who are getting into blocks. 我担心这个问题非常基本,但我认为这与很多进入数据块的Objective-C程序员有关。

What I've heard is that since blocks capture local variables referenced within them as const copies, using self within a block can result in a retain cycle, should that block be copied. 我听到的是,因为块捕获它们作为const副本引用的局部变量,所以在块中使用self可以导致保留周期,如果该块被复制。 So, we are supposed to use __block to force the block to deal directly with self instead of having it copied. 所以,我们应该使用__block来强制块直接处理self而不是复制它。

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

instead of just 而不仅仅是

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

What I'd like to know is the following: if this is true, is there a way that I can avoid the ugliness (aside from using GC)? 我想知道的是:如果这是真的,有没有办法可以避免丑陋(除了使用GC)?

Strictly speaking, the fact that it's a const copy has nothing to do with this problem. 严格来说,它是一个const副本的事实与这个问题无关。 Blocks will retain any obj-c values that are captured when they are created. 块将保留创建时捕获的任何obj-c值。 It just so happens that the workaround for the const-copy issue is identical to the workaround for the retain issue; 恰好,const-copy问题的解决方法与保留问题的解决方法相同; namely, using the __block storage class for the variable. 即,使用__block存储类作为变量。

In any case, to answer your question, there's no real alternative here. 无论如何,要回答你的问题,这里没有真正的选择。 If you're designing your own block-based API, and it makes sense to do so, you could have the block get passed the value of self in as an argument. 如果你正在设计自己的基于块的API,并且这样做是有意义的,你可以让块作为参数传递self in的值。 Unfortunately, this doesn't make sense for most APIs. 不幸的是,这对大多数API来说没有意义。

Please note that referencing an ivar has the exact same issue. 请注意,引用ivar具有完全相同的问题。 If you need to reference an ivar in your block, either use a property instead or use bself->ivar . 如果您需要在块中引用ivar,请使用属性代替或使用bself->ivar


Addendum: When compiling as ARC, __block no longer breaks retain cycles. 附录:当编译为ARC时, __block不再中断保留周期。 If you're compiling for ARC, you need to use __weak or __unsafe_unretained instead. 如果您正在为ARC编译,则需要使用__weak__unsafe_unretained

Just use: 只需使用:

__weak id weakSelf = self;

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

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

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

Note: if that doesn't work you can try 注意:如果这不起作用,您可以尝试

__weak typeof(self)weakSelf = self;

This might be obvious, but you only have to do the ugly self alias when you know you'll get a retain cycle. 这可能是显而易见的,但是当你知道你会得到一个保留周期时,你只需要做一个丑陋的self别名。 If the block is just a one-shot thing then I think you can safely ignore the retain on self . 如果块只是一次性的东西,那么我认为你可以放心地忽略self保留。 The bad case is when you have the block as a callback interface, for example. 例如,当您将块作为回调接口时,不好的情况就是如此。 Like here: 像这儿:

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

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

@implementation AudioProcessor

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

Here the API does not make much sense, but it would make sense when communicating with a superclass, for example. 这里的API没有多大意义,但是在与超类通信时也是有意义的。 We retain the buffer handler, the buffer handler retains us. 我们保留缓冲区处理程序,缓冲区处理程序保留了我们。 Compare with something like this: 比较这样的事情:

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];
    }];
}

In these situations I don't do the self aliasing. 在这些情况下,我不做self混叠。 You do get a retain cycle, but the operation is short-lived and the block will get out of memory eventually, breaking the cycle. 你确实得到一个保留周期,但是操作是短暂的,并且块最终会从内存中断开,从而打破周期。 But my experience with blocks is very small and it might be that self aliasing comes out as a best practice in the long run. 但是我对块的体验非常小,从长远来看, self混叠可能是最佳实践。

Posting another answer because this was a problem for me too. 发布另一个答案,因为这对我来说也是一个问题。 I originally thought I had to use blockSelf anywhere there was a self reference inside a block. 我原本以为我必须在块内部有自引用的任何地方使用blockSelf。 This is not the case, it is only when the object itself has a block in it. 情况并非如此,只有当对象本身有一个块时才会出现。 And in fact, if you use blockSelf in these cases the object can get dealloc'd before you get the result back from the block and then it will crash when it tries to call it, so clearly you want self to be retained until the response comes back. 事实上,如果你在这些情况下使用blockSelf,那么在你从块中获得结果之前,对象可以被释放,然后当它试图调用它时它会崩溃,所以很明显你希望自己被保留直到响应回来。

First case demonstrates when a retain cycle will occur because it contains a block which is referenced in the block: 第一种情况说明何时会发生保留周期,因为它包含块中引用的块:

#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];
}

You don't need blockSelf in the second case because the calling object does not have a block in it that will cause a retain cycle when you reference self: 在第二种情况下你不需要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;
} 

Remember also that retain cycles can occur if your block refers to another object which then retains self . 还要记住,如果您的块引用另一个保留self对象,则可能会发生保留周期。

I'm not sure that Garbage Collection can help in these retain cycles. 我不确定垃圾收集可以帮助这些保留周期。 If the object retaining the block (which I'll call the server object) outlives self (the client object), the reference to self inside the block will not be considered cyclic until the retaining object itself is released. 如果保留块的对象(我称之为服务器对象)超过self (客户端对象),则在释放保留对象本身之前,块内对self的引用将不会被视为循环。 If the server object far outlives its clients, you may have a significant memory leak. 如果服务器对象远远超过其客户端,则可能会发生严重的内存泄漏。

Since there are no clean solutions, I would recommend the following workarounds. 由于没有干净的解决方案,我建议采用以下解决方法。 Feel free to choose one or more of them to fix your issue. 随意选择其中一个或多个来解决您的问题。

  • Use blocks only for completion , and not for open-ended events. 仅使用块来完成 ,而不是用于开放式事件。 For example, use blocks for methods like doSomethingAndWhenDoneExecuteThisBlock: , and not methods like setNotificationHandlerBlock: . 例如,对doSomethingAndWhenDoneExecuteThisBlock:等方法使用块,而不使用setNotificationHandlerBlock:等方法。 Blocks used for completion have definite ends of lives, and should be released by server objects after they are evaluated. 用于完成的块具有明确的生命终结,并且应在评估后由服务器对象释放。 This prevents the retain cycle from living for too long even if it occurs. 这可以防止保留周期长时间存活,即使它发生。
  • Do that weak-reference dance you described. 做你描述的弱参考舞蹈。
  • Provide a method to clean up your object before it's released, which "disconnects" the object from server objects that may hold references to it; 提供一种在对象释放之前清理对象的方法,该对象将对象与可能包含对象的服务器对象“断开连接”; and call this method before calling release on the object. 并在调用对象上的release之前调用此方法。 While this method is perfectly fine if your object only has one client (or is a singleton within some context), but will break down if it has multiple clients. 虽然如果您的对象只有一个客户端(或者在某些上下文中是单例),但这种方法非常好,但如果它有多个客户端则会崩溃。 You're basically defeating the retain-counting mechanism here; 你基本上打败了保留计数机制; this is akin to calling dealloc instead of release . 这类似于调用dealloc而不是release

If you are writing a server object, take block arguments only for completion. 如果要编写服务器对象,请仅使用块参数完成。 Do not accept block arguments for callbacks, such as setEventHandlerBlock: . 不接受回调的块参数,例如setEventHandlerBlock: . Instead, fall back to the classic delegate pattern: create a formal protocol, and advertise a setEventDelegate: method. 相反,回到经典的委托模式:创建一个正式的协议,并宣传一个setEventDelegate:方法。 Do not retain the delegate. 不要保留代表。 If you don't even want to create a formal protocol, accept a selector as a delegate callback. 如果您甚至不想创建正式协议,请接受选择器作为委托回调。

And lastly, this pattern should ring alarms: 最后,这种模式应响铃:

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

If you're trying to unhook blocks that may refer to self from inside dealloc , you're already in trouble. 如果你试图解开可能从dealloc内部引用self块,那你就已经遇到了麻烦。 dealloc may never be called due to the retain cycle caused by references in the block, which means that your object is simply going to leak until the server object is deallocated. 由于块中的引用引起的保留周期, dealloc可能永远不会被调用,这意味着您的对象只是泄漏,直到服务器对象被释放。

__block __unsafe_unretained modifiers suggested in Kevin's post may cause to the bad access exception in case of block executed in a different thread. Kevin的帖子中建议的__block __unsafe_unretained修饰符可能导致在不同线程中执行块的情况下的错误访问异常。 It's better use only __block modifier for the temp variable and make it nil after the usage. 最好只使用__block修饰符作为临时变量,并在使用后使其为零。

__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;
}];

You can use libextobjc library. 您可以使用libextobjc库。 It is quite popular, it is used in ReactiveCocoa for example. 它很受欢迎,例如在ReactiveCocoa中使用它。 https://github.com/jspahrsummers/libextobjc https://github.com/jspahrsummers/libextobjc

It provides 2 macros @weakify and @strongify, so you can have: 它提供了2个宏@weakify和@strongify,因此您可以:

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

This prevents a direct strong reference so we don't get into a retain cycle to self. 这可以防止直接强引用,因此我们不会进入自我保留周期。 And also, it prevents self from becoming nil half-way, but still properly decrements the retain count. 而且,它会阻止self在中途变为零,但仍然可以正确地减少保留计数。 More in this link: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html 更多链接: http//aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

How about this? 这个怎么样?

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

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

I don't get the the compiler warning anymore. 我不再收到编译器警告了。

Block: a retain cycle will occur because it contains a block which is referenced in the block; 块:将发生保留周期,因为它包含块中引用的块; If you make the block copy and use a member variable,self will retain. 如果您进行块复制并使用成员变量,则self将保留。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM