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.
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 message开发者_运维技巧WithBlock:^{ [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)?
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.
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;
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.
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;
}
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 likesetNotificationHandlerBlock:
. 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 ofrelease
.
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.
__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;
}];
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
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.
精彩评论