Should I use weakSelf in nested blocks?
Asked Answered
R

4

13

I'm trying to correctly avoid retain cycles with blocks in Objective C, and am not sure about having nested blocks.

If I write a simple block like this:

[self doSomethingWithBlock:^{
    [self doSomethingElse];
}];

The compiler catches and warns me that this could cause retain cycles. I change it as follows to avoid the cycle:

__weak __typeof(self)weakSelf = self;
[self doSomethingWithBlock:^{
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    [strongSelf doSomethingElse];
}];

When I write something like this:

[self doSomethingWithBlock:^(MyObject* object){
    [object doSomethingElseWithBlock:^{
        [self doYetAnotherThing];
    }];
}];

The compiler is happy, but I'm not convinced that it's safe. Even though there is an intermediary object in between, it still looks conceptually the same as above, but now it's a cycle with 3 retains.

Should it be like this instead?

[self doSomethingWithBlock:^(MyObject* object){
    __weak __typeof(self)weakSelf = self;
    [object doSomethingElseWithBlock:^{
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf doYetAnotherThing];
    }];
}];

Or like this?

__weak __typeof(self)weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    [object doSomethingElseWithBlock:^{
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf doYetAnotherThing];
    }];
}];
Resumption answered 8/8, 2016 at 18:7 Comment(2)
Does the block get copied or retained by self at any point? Or is it retained indirectly by another object that self retains? If not, then there's no cycle, no matter how deep the nesting.Beforetime
@danh: The cycle isn't the problem. Keeping self alive when it's not needed is the problem.Sammie
S
10

In this situation, you are not worried about cyclic references. What you are worried about is a situation where the object self isn't actually needed anymore, but using self inside a nested block would keep it unnecessarily alive. For example, if you have a view controller that should go away when the view is removed by the screen, but you download an image that you would like to display in the controllers view. If the image arrives long after the view is already gone, you don't want the view controller alive anymore.

Best is

__weak typeof (self) weakSelf = self;

before calling the outermost method. Then within every block that ought to use self, you add

typeof (self) strongSelf = weakSelf;

and use strongSelf within that block. Depending on the situation, you might want to check that strongSelf isn't nil at that point, but sending messages to strongSelf when it is nil has no effect, so if all you do is sending messages and getting or setting properties, then a check for nil is not necessary.

What happens if you don't do this? The difference will be that self may be kept alive unnecessarily into the innermost block, if you use self everywhere (or just in the innermost block).

Sammie answered 8/8, 2016 at 22:33 Comment(1)
Thanks, this is a very thorough explanation!Resumption
P
1

You should not capture something weakly just because you get a warning from the compiler; the compiler warning is just guessing; it doesn't know how the methods you call make the references. You should only do this if you understand the architecture of the references and determine that there is a cycle and determine that capturing a weak reference instead still preserves the intended behavior. You haven't shown us the code of -doSomethingWithBlock:. It would only create a retain cycle if inside that method it assigns the block to a property or instance variable of self. Does it do that or not? If not, then there is no retain cycle, and there is no point to the outer block capturing self weakly.

Assuming that the outer block capturing self weakly is right, the examples where the outer block captures self strongly are out of the question. The remaining questions would be whether the inner block should capture self (or whatever version of self, e.g. strongSelf, is appropriate) strongly. In other words, whether you would do something like this:

__weak __typeof(self) weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [object doSomethingElseWithBlock:^{
            [strongSelf doYetAnotherThing];
        }];
    }
}];

or something like this:

__weak __typeof(self) weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [object doSomethingElseWithBlock:^{
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            if (strongSelf) {
                [strongSelf doYetAnotherThing];
            }
        }];
    }
}];

Again, the main issue to determine is whether there is a retain cycle if the inner block captures self strongly. There would only be a retain cycle if [object doSomethingElseWithBlock:... somehow assigns the block to a property or instance variable of self. But how could it? The method is called on object, not self. The method does not get self in any way. Unless there is something complicated going on, the method is not going to assign to a property or instance variable of self, so it is unlikely to create a retain cycle. This means that the inner block capturing self weakly is not necessary to prevent a retain cycle.

But whether the inner block captures self weakly or strongly could affect the behavior. Namely, if the inner block captures self weakly, self could be deallocated by the time the block is run, in which case [strongSelf doYetAnotherThing]; will not be executed, whereas if the inner block captured self strongly, it would keep self alive and [strongSelf doYetAnotherThing]; would be executed. So it depends on what -doYetAnotherThing does. If it performs some UI operation on self which is a UI view or something, then whether you perform it on a view that is no longer displayed doesn't make a difference. But if it for example sends something to the network or something, then whether or not it is executed can make a big difference.

Pharyngo answered 13/9, 2016 at 8:44 Comment(0)
R
0

Look at the answer for this question.

I'd do this

__weak typeof (self) weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    [object doSomethingElseWithBlock:^{
        [weakSelf doYetAnotherThing];
    }];
}];
Rubato answered 8/8, 2016 at 18:50 Comment(0)
F
0

Xcode 8 beta 4 underlines the self keyword, and warns of a possible retain cycle for having used it inside the block.

Per Apple Developer Connection's Programming in Objective-C (Working with Blocks):

Avoid Strong Reference Cycles when Capturing self If you need to capture self in a block, such as when defining a callback block, it’s important to consider the memory management implications.

Blocks maintain strong references to any captured objects, including self, which means that it’s easy to end up with a strong reference cycle if, for example, an object maintains a copy property for a block that captures self:

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
    self.block = ^{
        [self doSomething];    // capturing a strong reference to self
                               // creates a strong reference cycle
    };
}
...
@end

The compiler will warn you for a simple example like this, but a more complex example might involve multiple strong references between objects to create the cycle, making it more difficult to diagnose.

To avoid this problem, it’s best practice to capture a weak reference to self, like this:

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
        [weakSelf doSomething];   // capture the weak reference
                                  // to avoid the reference cycle
    }
}

By capturing the weak pointer to self, the block won’t maintain a strong relationship back to the XYZBlockKeeper object. If that object is deallocated before the block is called, the weakSelf pointer will simply be set to nil.

This site reportedly provides a means for making the self keyword weak whenever it's used inside a block; it also provides instructions for returning a weak self or class object formerly strong, strong again:

https://coderwall.com/p/vaj4tg/making-all-self-references-in-blocks-weak-by-default

Fouquiertinville answered 8/8, 2016 at 22:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.