Always pass weak reference of self into block in ARC?

转自出处 : http://stackoverflow.com/questions/20030873/always-pass-weak-reference-of-self-into-block-in-arc


I am a little confused about block usage in Objective-C. I currently use ARC and I have quite a lot of blocks in my app, currently always referring to self instead of its weak reference. May that be the cause of these blocks retaining self and keeping it from being dealloced ? The question is, should I always use aweak reference of self in a block ?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}
share | improve this question
 
 
If you want an in depth discourse on this topic read dhoerl.wordpress.com/2013/04/23/… –   David H  Nov 17 '13 at 13:04

4 Answers

up vote 109 down vote accepted

It helps not to focus on the strong or weak part of the discussion. Instead focus on the cycle part.

A retain cycle is a loop that happens when Object A retains Object B, and Object B retains Object A. In that situation, if either object is released:

  • Object A won't be deallocated because Object B holds a reference to it.
  • But Object B won't ever be deallocated as long as Object A has a reference to it.
  • But Object A will never be deallocated because Object B holds a reference to it.
  • ad infinitum

Thus, those two objects will just hang around in memory for the life of the program even though they should, if everything were working properly, be deallocated.

So, what we're worried about is retain cycles, and there's nothing about blocks in and of themselves that create these cycles. This isn't a problem, for example:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

The block retains self, but self doesn't retain the block. If one or the other is released, no cycle is created and everything gets deallocated as it should.

Where you get into trouble is something like:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

Now, your object (self) has an explicit strong reference to the block. And the block has an implicitstrong reference to self. That's a cycle, and now neither object will be deallocated properly.

Because, in a situation like this, self by definition already has a strong reference to the block, it's usually easiest to resolve by making an explicitly weak reference to self for the block to use:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

But this should not be the default pattern you follow when dealing with blocks that call self! This should only be used to break what would otherwise be a retain cycle between self and the block. If you were to adopt this pattern everywhere, you'd run the risk of passing a block to something that got executed after self was deallocated.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];
share | improve this answer
 
6  
Very good answer! –   Leo Natan  Nov 17 '13 at 15:02
 
Thanks, that indeed is a perfect answer! –   the_critic  Nov 17 '13 at 15:04
 
I am not sure A retain B, B retain A will do a infinite cycle. From the perspective of reference count, A and B's reference count is 1. What cause a retain cycle for this situation is when there is no other group have strong reference of A and B outside -- it means we cannot reach these two object(we cannot control A to release B and vice versa), therefor, A and B reference each other to keep themselves both alive. –   Danyun  Nov 17 '13 at 17:20
 
@Danyun While it's true that a retain cycle between A and B is not unrecoverable until all other references to these objects have been released, that doesn't make it any less a cycle. Conversely, just because a particular cycle might be recoverable doesn't mean it's ok to have it in your code. Retain cycles are a smell of bad design. –   jemmons  Nov 17 '13 at 22:32
1  
@Master It's impossible for me to say. It depends completely on the implementation of your -setCompleteionBlockWithSuccess:failure: method. But if paginator is owned byViewController, and these blocks don't get called after ViewController would be released, using a__weak reference would be the safe move (because self owns the thing that owns the blocks, and so is likely to still be around when the blocks call it even though they don't retain it). But that's a lot of "if"s. It really depends on what this is supposed to do. –   jemmons  Oct 3 at 19:27 

You don't have to always use a weak reference. If your block is not retained, but executed and then discarded, you can capture self strongly, as it will not create a retain cycle. In some cases, you even want the block to hold the self until the completion of the block so it does not deallocate prematurely. If, however, you capture the block strongly, and inside capture self, it will create a retain cycle.

share | improve this answer
 
 
Well I just execute the block as a callback and I would not want self to be dealloced at all. But it seems as if I were creating retain cycles because the View Controller in question does not get dealloced... –   the_critic Nov 17 '13 at 13:00
 
For example, if you have a bar button item which is retained by the view controller, and you capture self strongly in that block, there will be a retain cycle. –   Leo Natan  Nov 17 '13 at 13:01
1  
@MartinE. You should just update your question with code samples. Leo is quite right (+1), that it doesn't necessarily cause strong reference cycle, but it may, depending upon how you use these blocks. It will be easier for us to help you if you provide code snippets. –   Rob  Nov 17 '13 at 13:06 
 
@Rob I added some code to my question. –   the_critic  Nov 17 '13 at 13:14
 
@LeoNatan I understand the notion of retain cycles, but I am not quite sure what happens in blocks, so that confuses me a little bit –   the_critic  Nov 17 '13 at 13:16

As Leo points out, the code you added to your question would not suggest a strong reference cycle (a.k.a., retain cycle). One operation-related issue that could cause a strong reference cycle would be if the operation is not getting released. While your code snippet suggests that you have not defined your operation to be concurrent, but if you have, it wouldn't be released if you never posted isFinished, or if you had circular dependencies, or something like that. And if the operation isn't released, the view controller wouldn't be released either. I would suggest adding a breakpoint or NSLog in your operation's dealloc method and confirm that's getting called.

You said:

I understand the notion of retain cycles, but I am not quite sure what happens in blocks, so that confuses me a little bit

The retain cycle (strong reference cycle) issues that occur with blocks are just like the retain cycle issues you're familiar with. A block will maintain strong references to any objects that appear within the block, and it will not release those strong references until the block itself is released. Thus, if block references self, or even just references an instance variable of self, that will maintain strong reference to self, that is not resolved until the block is released (or in this case, until the NSOperation subclass is released.

For more information, see the Avoid Strong Reference Cycles when Capturing self section of theProgramming with Objective-C: Working with Blocks document.

If your view controller is still not getting released, you simply have to identify where the unresolved strong reference resides (assuming you confirmed the NSOperation is getting deallocated). A common example is the use of a repeating NSTimer. Or some custom delegate or other object that is erroneously maintaining a strong reference. You can often use Instruments to track down where objects are getting their strong references, e.g.:

record reference counts

share | improve this answer
 
1  
Another example would be if the operation is retained in the block creator and not released once it is finished. +1 on the nice write up! –   Leo Natan  Nov 17 '13 at 14:37 
 
@LeoNatan Agreed, though the code snippet represents it as a local variable which would be released if he's using ARC. But you're quite right! –   Rob  Nov 17 '13 at 14:42 
1  
Yes, I was just giving an example, as the OP requested in the other answer. –   Leo Natan  Nov 17 '13 at 14:42

Some explanation ignore a condition about the retain cycle [If a group of objects is connected by a circle of strong relationships, they keep each other alive even if there are no strong references from outside the group.] For more information, read the document

share | improve this answer
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值