ChatGPT解决这个技术问题 Extra ChatGPT

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 a weak 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);
    }
}
If you want an in depth discourse on this topic read dhoerl.wordpress.com/2013/04/23/…

j
jemmons

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 implicit strong 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];
}];

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 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 Yes, we should always avoid a retain cycle design as much as we can.
@Master It's impossible for me to say. It depends completely on the implementation of your -setCompleteionBlockWithSuccess:failure: method. But if paginator is owned by ViewController, 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.
@Jai No, and this is at the heart of the memory management problem with blocks/closures. Objects get deallocated when nothing owns them. MyObject and SomeOtherObject both own the block. But because the block's reference back to MyObject is weak, the block does not own MyObject. So while the block is guaranteed to exist as long as either MyObject or SomeOtherObject exist, there's no guarantee that MyObject will exist as long as the block does. MyObject can be completely deallocated and, as long as SomeOtherObject still exists, the block will still be there.
I
Iulian Onofrei

I totally agree with @jemmons:

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

To overcome this problem one can define a strong reference over the weakSelf inside the block:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

Wouldn't strongSelf increment the reference count for weakSelf? Thus creating a retain cycle?
Retain cycles only matter if they exist in the static state of the object. While code is executing and its state is in flux, multiple and possibly redundant retains are fine. Anyway regarding this pattern, capturing a strong reference there doesn't do anything for the case of self getting deallocated before the block runs, that can still happen. It does ensure self doesn't get deallocated while executing the block. This matters if the block does async operations itself giving a window for that to happen.
@smallduck, your explanation is great. Now I understand this better. Books not covered this, thanks.
This isn't a good example of strongSelf, because the explicit addition of strongSelf is exactly what the runtime would do anyway: on the doSomething line, a strong reference is taken for the duration of the method call. If weakSelf was already invalidated, the strong ref is nil and the method call is a no-op. Where strongSelf helps is if you have a series of operations, or accessing a member field (->), where you want to guarantee you actually got a valid reference and hold it continuously across the whole set of operations, e.g. if ( strongSelf ) { /* several operations */ }
L
Léo Natan

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.


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...
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.
@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.
@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
In the above code, your self instance will be released once the operation finishes and the block is released. You should read up on how blocks work and what and when they capture in their scope.
R
Rob

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 the Programming 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.:

https://i.stack.imgur.com/y0eLE.png

Or in Xcode 5:

https://i.stack.imgur.com/kn1sF.png


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!
@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!
Yes, I was just giving an example, as the OP requested in the other answer.
By the way, Xcode 8 has "Debug Memory Graph", which is an even easier ways to find strong references to objects that have not been released. See stackoverflow.com/questions/30992338/….
D
Danyun Liu

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


R
Ranjeet Singh

This is how you can use the self inside the block:

//calling of the block

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};