My block is not retaining some of its objects
From the Blocks documentation:
In a reference-counted environment, by default when you reference an Objective-C object within a block, it is retained. This is true even if you simply reference an instance variable of the object.
I am trying to implement a completion handler pattern, where a block is given to an object before the work is performed and the block is executed by the receiver after the work is performed. Since I am being a good memory citizen, the block should own the objects it references in the completion handler and then they will be released when the block goes out of scope. 开发者_如何学运维I know enough to know that I must copy
the block to move it to the heap since the block will survive the stack scope in which it was declared.
However, one of my objects is getting deallocated unexpectedly. After some playing around, it appears that certain objects are not retained when the block is copied to the heap, while other objects are. I am not sure what I am doing wrong. Here's the smallest test case I can produce:
typedef void (^ActionBlock)(UIView*);
In the scope of some method:
NSObject *o = [[[NSObject alloc] init] autorelease];
mailViewController = [[[MFMailComposeViewController alloc] init] autorelease];
NSLog(@"o's retain count is %d",[o retainCount]);
NSLog(@"mailViewController's retain count is %d",[mailViewController retainCount]);
ActionBlock myBlock = ^(UIView *view) {
[mailViewController setCcRecipients:[NSArray arrayWithObjects:@"test@recipient.com",nil]];
[o class];
};
NSLog(@"mailViewController's retain count after the block is %d",[mailViewController retainCount]);
NSLog(@"o's retain count after the block is %d",[o retainCount]);
Block_copy(myBlock);
NSLog(@"o's retain count after the copy is %d",[o retainCount]);
NSLog(@"mailViewController's retain count after the copy is %d",[mailViewController retainCount]);
I expect both objects to be retained by the block at some point, and I certainly expect their retain counts to be identical. Instead, I get this output:
o's retain count is 1
mailViewController's retain count is 1
mailViewController's retain count after the block is 1
o's retain count after the block is 1
o's retain count after the copy is 2
mailViewController's retain count after the copy is 1
o
(subclass of NSObject
) is getting retained properly and will not go out of scope. However mailViewController
is not retained and will be deallocated before the block is run, causing a crash.
Do not use -retainCount.
The absolute retain count of an object is meaningless.
You should call release
exactly same number of times that you caused the object to be retained. No less (unless you like leaks) and, certainly, no more (unless you like crashes).
See the Memory Management Guidelines for full details.
(cribbed from one of @bbum's answers)
As for your question:
Are you actually observing a crash? Or are you just shooting blindly from the hip and thinking that it might crash?
From the code you posted, it would appear that mailViewController
is an instance variable, in which case the block would be retaining self
instead of the instance variable. And since you autoreleased
your instance variable, it's getting cleaned up by an NSAutoreleasePool
just like you'd expect.
So in summary:
- Do not use
-retainCount
. - Do not
autorelease
instance variables that you want to exist beyond this turn of the run loop.
edit A bit more clarification:
Here's what's going to happen when your block is created:
- Inspect object references in its scope. There are two:
self->mailViewController
ando
. self->mailViewController
is a member of a struct (self
), so it is not retained directly. Retainself
instead.o
is a local variable. Retain it.
This is proper behavior. As for your code...
o
is created with a +0 retain countself->mailViewController
is created with a +0 retain countmyBlock
is created with a +0 retain count.o
now has a +1 RC, as doesself
.self->mailViewController
still has a +0 RCmyBlock
is copied => +1 retain count
Fast forward to the end of this run loop cycle.
- The current autorelease pool is drained. All objects with a +0 retain count are deallocated, including
self->mailViewController
.self->mailViewController
now points to deallocated memory, and is essentially garbage.
Fast forward to some future point when myBlock
is executed
myBlock
attempts to invoke a method onself->mailViewController
. However,self->mailViewController
no longer points to a valid object, and your app crashes.
However, if mailViewController
is not an instance variable, then we need to see more code. I think it'd be highly unlikely that the behavior you're seeing is a problem with the blocks runtime, but it is possible.
The documentation no longer says that. It now correctly says:
In a manually reference-counted environment, local variables used within the block are retained when the block is copied.
As the "mailViewController" is a member of your current class instance, so the block actually retain "self" here.
精彩评论