开发者

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:

  1. Do not use -retainCount.
  2. 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:

  1. Inspect object references in its scope. There are two: self->mailViewController and o.
  2. self->mailViewController is a member of a struct (self), so it is not retained directly. Retain self instead.
  3. o is a local variable. Retain it.

This is proper behavior. As for your code...

  1. o is created with a +0 retain count
  2. self->mailViewController is created with a +0 retain count
  3. myBlock is created with a +0 retain count. o now has a +1 RC, as does self. self->mailViewController still has a +0 RC
  4. myBlock is copied => +1 retain count

Fast forward to the end of this run loop cycle.

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

  1. myBlock attempts to invoke a method on self->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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜