Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS Proper Use of @weakify(self) and @strongify(self)

I'm starting to integrate libextobjc (https://github.com/jspahrsummers/libextobjc) into my iOS application primarily to take advantage of EXTScope's @strongify and @weakify, but have a few questions before proceeding too deep into the process.

Here's an example that's intentionally overly-complicated to try to suss out how to handle this:

- (void)someMethod {
    if (self.someBOOL) {
        _someObjectInstanceVar = [Object objectWithCompletionHandler:^{
            // self reference #1
            if (self.someProperty) {
                // self reference #2
                [[Async HTTPRequest] sendAWithID:self.property.id completionHandler:^(void (^)(NewObject *newObject) {
                    // self reference #3
                    [self showViewWithObject:newObject handler:^{
                        // self reference #4
                        [self reloadData];
                    }];
                }];
            }
        }];

    else {
        [[Async HTTPRequest] sendBWithID:self.property.id completionHandler:^{
            // self reference #5
            [self reloadData];
        }];
    }
}

My understanding is that if I want to do anything like an async HTTP request, and inside the completion handler reference self, like [self reloadData], I don't need to do anything with strong/weak as the request block itself isn't retaining the completion block, so there's no problems with retain cycles there. In the above code example, I think #5 is a case where a retain cycles isn't an issue.

The main concern are all of the objects that take a block as a property/init param, that are internally holding onto the block properties. Inside the objectWithCompletionHandler method, where someObject holds onto the completionHandler block as an instance variable, there are multiple references to self there that I do know would cause a leak. My main question is in such a case, how would you need to handle weakify and strongify to make it "safer"? Would one @weakify and @strongify call each be sufficient, like the following:

- (void)someMethod {
    @weakify (self);

    _someObjectInstanceVar = [Object objectWithCompletionHandler:^{
        @strongify(self);
    ...
}

Would the above @strongify(self) reference be sufficient to use for self references #1, 2, 3, and 4, or do I have to (and would it even work) get a new weak/strong reference to use inside the sendAWithID method and the nested reloadData?

EDIT: Fixed code to have question make more sense and fix some syntax errors.

like image 795
Mike Avatar asked Feb 03 '15 17:02

Mike


2 Answers

How @strongify works

After @strongify is called, self will have a different pointer address inside the block than it will outside the block. That's because @strongify declares a new local variable called self each time. (This is why it suppresses the -Wshadow warning, which will “warn whenever a local variable shadows another local variable.”) It's worth reading and understanding the implementation of these functions. So even though the names are the same, treat them as separate strong references.

Using @strongify in your code

Presupposing (which is not true) that each use of a block would create a reference cycle, you could:

- (void)someMethod {
    if (self.someBOOL) {
        @weakify(self);
        _someObjectInstanceVar = [Object objectWithCompletionHandler:^{
            @strongify(self);
            // self reference #1
            if (self.someProperty) {
                @weakify(self);
                // self reference #2
                [[Async HTTPRequest] sendAWithID:self.property.id completionHandler:^(void (^)(NewObject *newObject) {
                    @strongify(self);
                    // self reference #3
                    @weakify(self);
                    [self showViewWithObject:newObject handler:^{
                        // self reference #4
                        @strongify(self);
                        [self reloadData];
                    }];
                }];
            }
        }];
    // etc…
}

However, remember that after your first use of @strongify, self will refer to local, stack variables. These will typically get destroyed when the scope in which they're defined ends (as long as you aren't storing them to properties or using them in a nested block). So based on the code you showed, you only need it after // self reference #1.

See Also

Reading the unit test covering @weakify and @strongify will help clarify the correct usage of these functions.

like image 177
Aaron Brager Avatar answered Nov 12 '22 13:11

Aaron Brager


To answer your question of whether multiple instances of weakify/strongify in each nested level of your blocks works, then yes. But there's no need to do that because, your first @strongify definition already defines self for all of the inner scope of your block (and the blocks that are nested in it).

However, given that your blocks have different lifetimes, you might want to add @strongify for each nested block to make sure they all hold their own retain cycle to their inner scope.

Here's the github issue thread that explains this case: https://github.com/jspahrsummers/libextobjc/issues/45

like image 8
carlossless Avatar answered Nov 12 '22 13:11

carlossless