用块在“self”上保留循环

发布于 2024-10-05 23:31:18 字数 449 浏览 3 评论 0原文

恐怕这个问题非常基础,但我认为它与许多陷入困境的 Objective-C 程序员相关。

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

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

而不仅仅是

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

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

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.

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. So, we are supposed to use __block to force the block to deal directly with self instead of having it copied.

__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)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

朮生 2024-10-12 23:31:18

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

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

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


附录:当编译为 ARC 时,__block 不再破坏保留循环。如果您要针对 ARC 进行编译,则需要使用 __weak__unsafe_unretained

Strictly speaking, the fact that it's a const copy has nothing to do with this problem. Blocks will retain any obj-c values that are captured when they are created. It just so happens that the workaround for the const-copy issue is identical to the workaround for the retain issue; namely, using the __block storage class for the variable.

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. Unfortunately, this doesn't make sense for most APIs.

Please note that referencing an ivar has the exact same issue. If you need to reference an ivar in your block, either use a property instead or use bself->ivar.


Addendum: When compiling as ARC, __block no longer breaks retain cycles. If you're compiling for ARC, you need to use __weak or __unsafe_unretained instead.

四叶草在未来唯美盛开 2024-10-12 23:31:18

只需使用:

__weak id weakSelf = self;

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

了解更多信息:WWDC 2011 - Blocks 和 Grand Central Dispatch 实践

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

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

__weak typeof(self)weakSelf = self;

Just use:

__weak id weakSelf = self;

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

For more information: WWDC 2011 - Blocks and Grand Central Dispatch in Practice.

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

Note: if that doesn't work you can try

__weak typeof(self)weakSelf = self;
心病无药医 2024-10-12 23:31:18

这可能是显而易见的,但当您知道将获得保留周期时,您只需使用丑陋的 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 别名。您确实获得了保留周期,但该操作是短暂的,并且该块最终将耗尽内存,从而打破循环。但我对块的经验非常少,从长远来看,自我别名可能是最佳实践。

This might be obvious, but you only have to do the ugly self alias when you know you’ll get a retain cycle. If the block is just a one-shot thing then I think you can safely ignore the retain on 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. 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. 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.

莳間冲淡了誓言ζ 2024-10-12 23:31:18

发布另一个答案,因为这对我来说也是一个问题。我最初认为我必须在块内有自引用的任何地方使用 blockSelf。事实并非如此,只有当对象本身有一个块时才会出现这种情况。事实上,如果您在这些情况下使用 blockSelf,该对象可能会在您从块返回结果之前被释放,然后当它尝试调用它时它会崩溃,所以显然您希望 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];
}

在第二种情况下,您不需要 blockSelf,因为调用对象中没有块,当您调用该对象时,将导致保留循环参考自:

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

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. 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.

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:

#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;
} 
停滞 2024-10-12 23:31:18

还要记住,如果您的块引用了另一个对象,然后该对象又保留了self,则可能会发生保留循环。

我不确定垃圾收集可以在这些保留周期中提供帮助。如果保留块的对象(我将其称为服务器对象)比 self(客户端对象)长,则块内对 self 的引用将不会被视为循环直到保留对象本身被释放。如果服务器对象的寿命远远超过其客户端,则可能会出现严重的内存泄漏。

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

  • 仅将块用于完成,而不是用于开放式事件。例如,将块用于 doSomethingAndWhenDoneExecuteThisBlock: 等方法,而不是 setNotificationHandlerBlock: 等方法。用于完成的块有明确的生命周期,并且应该在评估后由服务器对象释放。这可以防止保留周期即使发生也不会存在太久。
  • 做你描述的弱引用舞蹈。
  • 提供一种在对象释放之前清理对象的方法,该方法将对象与可能保存对其引用的服务器对象“断开”;并在调用对象的release之前调用此方法。如果您的对象只有一个客户端(或者在某些上下文中是单例),则此方法完全没问题,但如果它有多个客户端,则会崩溃。您基本上在这里击败了保留计数机制;这类似于调用 dealloc 而不是 release

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

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

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

如果您试图从 dealloc 内部取消可能引用 self 的块,那么您已经遇到麻烦了。由于块中的引用引起的保留循环,dealloc 可能永远不会被调用,这意味着您的对象只会泄漏,直到服务器对象被释放为止。

Remember also that retain cycles can occur if your block refers to another object which then retains 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. 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:. 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. 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.

If you are writing a server object, take block arguments only for completion. Do not accept block arguments for callbacks, such as setEventHandlerBlock:. Instead, fall back to the classic delegate pattern: create a formal protocol, and advertise a setEventDelegate: method. 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 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.

执笏见 2024-10-12 23:31:18

Kevin 的帖子中建议的 __block __unsafe_unretained 修饰符可能会在块执行的情况下导致错误的访问异常在不同的线程中。最好只对 temp 变量使用 __block 修饰符,并在使用后将其设置为 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;
}];

__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. It's better use only __block modifier for the temp variable and make it nil after the usage.

__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;
}];
时间你老了 2024-10-12 23:31:18

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

它提供了2个宏@weakify和@strongify,所以你可以有:

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

这可以防止直接强引用,这样我们就不会进入 self 的保留循环。而且,它可以防止 self 中途变为 nil,但仍然适当地减少保留计数。
更多内容请参见此链接:
http://aceontech .com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

You can use libextobjc library. It is quite popular, it is used in ReactiveCocoa for example.
https://github.com/jspahrsummers/libextobjc

It provides 2 macros @weakify and @strongify, so you can have:

@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.
More in this link:
http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

(り薆情海 2024-10-12 23:31:18

这个怎么样?

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

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

我不再收到编译器警告。

How about this?

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

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

I don't get the the compiler warning anymore.

自由如风 2024-10-12 23:31:18

Block:因为包含了一个block,并且在该block中引用了,所以会发生retain Cycle;
如果您进行块复制并使用成员变量,则 self 将保留。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文