47

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.

Fogmeister
  • 76,236
  • 42
  • 207
  • 306
Mike
  • 9,765
  • 5
  • 34
  • 59
  • I updated that variable created with the block to be an instance variable - that would cause a retain block, correct? Assuming the object holds onto that block internally... – Mike Apr 03 '15 at 16:37
  • For your "self reference #5", what should do when self is dealloc, and code execute to the CompletionHandler, it will crash when call self. – JerryZhou Jan 13 '17 at 04:08

4 Answers4

86

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.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • 1
    Each additional weakify() statetment isn't required after the first one. Read the link in my answer. Strongify increases the retain count on the initial weak reference, defining a new weak reference doesn't change anything. – carlossless Apr 03 '15 at 17:00
  • @carlossless That depends on whether you want the operation chain to continue if the initial reference becomes `nil` but the local reference still retains the object. My example assumes you do, but you are correct that it's not required if you don't care. – Aaron Brager Apr 03 '15 at 17:21
  • 1
    I'm not sure if your "however" is true, seems to contradict what jspahrsummers writes here: https://github.com/jspahrsummers/libextobjc/issues/45#issuecomment-38072317 – fabb Jan 15 '16 at 13:06
  • @fabb How so? I don't see a contradiction, in fact his statement that "the nested blocks typically don't create long-lived cycles" confirms my statement. You can just look at the source code to see that I'm correct. – Aaron Brager Jan 19 '16 at 20:49
  • "will get destroyed when the scope in which they're defined ends" => not true when self is used in a nested block. Yes, in the example above it would be ok, as no block property is set in a nested block, but it _could_ happen. – fabb Jan 20 '16 at 05:49
  • @fabb Ah, I see. I thought you were disagreeing with "self will refer to local, stack variables". I'll update my answer. – Aaron Brager Jan 20 '16 at 21:04
8

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

carlossless
  • 1,171
  • 8
  • 23
1

Calling "self" inside the block that in hold by "self" will lead to "Retain Cycles" and hence memory leaks. So ideally it goes like:

@interface A: NSObject // Some interface A

@property (nonatomic, copy, readwrite) dispatch_block_t someBlock; // See block is strongly retained here.

@end

*******************************************************
@implementation A

- (void) someAPI
{
    __weak A * weakSelf = self; // Assign self to weakSelf and use it
    // enter code here inside block to break retain cycles.
    self.someBlock = 
    ^{
        A * strongSelf = weakSelf; // Assign weak self to strongSelf before
       // using it. This is because weakSelf can go nil anytime and it may happen
       // that only few lines from block get executed before weakSelf goes nil,
       // and hence code may be in some bad state.
        if (strongSelf != nil)
        {
            // Use strongSelf.
            [strongSelf doSomethingAwesome];
            [strongSelf doSomethingAwesomeAgain];
        }
    };
}

@end

If the block is not retained by "self", then its safe to just use "self" inside blocks and they won't create retain-cycles.

Note: Memory management concept remains same with use of "libextobjc" library.

gagarwal
  • 4,224
  • 2
  • 20
  • 27
  • I understand the basics of this - my question is regarding slightly more convoluted/nested situations, similar to the example provided in my question. – Mike Apr 03 '15 at 17:45
0

[Not sure why there is no definitive answer on this question, but I got here today so here goes (maybe).]

You only need to weakify once, but you don't want to use the strong variable inside the next (or lower) nested block. If you do, you'll be retaining the variable inside that block. So the answer here is to strongify inside each asynchronous block. Don't use the strong self (or other variable) from the outer block.

Why? If the nested block is strongly holding self from the outer block, self cannot get deallocated until the inner block gets deallocated. If self is strongly holding the block, you have a proper retain loop or leak for the life of the application :(

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421