Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can using __weak attribute to pass parameter to blocks lead to memory leaks?

In my iOS ARC-enabled code, I need to pass "self" and other objects to a block. More specifically, I need to interact with self and an ASIHTTPRequest object inside the ASIHTTPRequest's completionBlock.

_operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(parseServerReply) object:nil];
_request = [ASIHTTPRequest requestWithURL:@"server.address"];

// ...

[_request setCompletionBlock:^{
    [self setResponseString:_request.responseString];
    [[MyAppDelegate getQueue] addOperation:_operation];
}];

In order to avoid the following warning: Capturing "self" strongly in this block will likely lead to a retain cycle. I have modified my code to add __weak attributes the the objects that are used in the block following this post: Fix warning "Capturing [an object] strongly in this block is likely to lead to a retain cycle" in ARC-enabled code

The resulting code is:

_operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(parseServerReply) object:nil];
_request = [ASIHTTPRequest requestWithURL:@"server.address"];

// ...
__weak NSOperation * operation = _operation;
__weak ASIHTTPRequest * request = _request;
__weak typeof(self) * self_ = self;

[request setCompletionBlock:^{
    [self_ setResponseString:request.responseString];
    [[MyAppDelegate getQueue] addOperation:operation];
}];

I want to know if this can still lead to retain cycle and memory leaks. If so, is there a way to avoid the leaks?

like image 828
gfrigon Avatar asked Dec 07 '22 14:12

gfrigon


1 Answers

You don't need to make everything weak, nor do want to keep referencing a weak object in the block (especially if the block can execute in a different thread).

Think of it this way. When using ARC, objects are reference counted. dealloc is not called on an object until the reference count goes to zero. So, as long as there is one reference around, the object will stay alive.

However, consider two objects that have a strong reference to each other. Neither will dealloc until the other releases its reference.

When you create a block, it captures its environment, and that means it will create strong references to any objects used in the scope of the block.

With that in mind...

id object = getMeSomeObject();
// <object> is implicitly __strong, and now has a reference.

The object will not get dealloc until all strong references are released. If you use it in a block, the block automatically creates its own strong reference to make sure the object lives as long as the block lives.

A __weak reference is a level of indirection that gives you access to an object AS LONG AS THE OBJECT LIVES. Basically, if you assign an object to a __weak pointer, that pointer is guaranteed to give you the same object, as long as that object is alive. once the object starts its own dealloc(), it finds all the __weak pointers and sets them to nil.

So, your _weak pointer will always be in one of two states. It points to a valid object as long as the object lives, or it is nil when the object has dealloc. You should never access an object through a weak pointer, because the object could dealloc behind your back, leaving you with a bad pointer.

So, what you want to do it create a __strong reference on the stack, so that the object stays alive as long as you want it.

In your case...

[_request setCompletionBlock:^{
    [self setResponseString:_request.responseString];
    [[MyAppDelegate getQueue] addOperation:_operation];
}];

This block obviously holds a strong reference to self. You probably do not want that. Let's try to fix it...

// weakSelf will be "magically" set to nil if <self> deallocs
__weak SelfType *weakSelf = self;
[_request setCompletionBlock:^{
    // If <self> is alive right now, I want to keep it alive while I use it
    // so I need to create a strong reference
    SelfType *strongSelf = weakSelf;
    if (strongSelf) {
        // Ah... <self> is still alive...
        [strongSelf setResponseString:_request.responseString];
        [[MyAppDelegate getQueue] addOperation:_operation];
    } else {
        // Bummer.  <self> dealloc before we could run this code.
    }
}];

Hey, we now have a weak self, but... you are still going to get the same problem. Why? Because _request and _operation are instance variables. If you access an instance variable inside a block, it implicitly creates a strong reference to self.

Giving it another go...

// weakSelf will be "magically" set to nil if <self> deallocs
__weak SelfType *weakSelf = self;
[_request setCompletionBlock:^{
    // If <self> is alive right now, I want to keep it alive while I use it
    // so I need to create a strong reference
    SelfType *strongSelf = weakSelf;
    if (strongSelf) {
        // Ah... <self> is still alive...
        [strongSelf setResponseString:strongSelf->_request.responseString];
        [[MyAppDelegate getQueue] addOperation:strongSelf->_operation];
    } else {
        // Bummer.  <self> dealloc before we could run this code.
    }
}];

Now, you probably should not be using instance variables "in the raw" but that's a different topic.

With these changes, you have a block that no longer has a strong reference to self, and if self does truly dealloc, it handles it gracefully.

Finally, I wil reiterate, the assignment to strongSelf is necessary to prevent potential problems where the object disappears after checking weakSelf. Specifically...

if (weakSelf) {
    // Hey, the object exists at the time of the check, but between that check
    // and the very next line, its possible that the object went away.
    // So, to prevent that, you should ALWAYS assign to a temporary strong reference.
    [weakSelf doSomething];
}

strongSelf = weakSelf;
// OK, now IF this object is not nil, it is guaranteed to stay around as long as
// strongSelf lives.

Now, in this case, the block is part of the request, which is part of self so the likelihood that self deallocs is small, but my main point here is to use self to prevent retain cycles, but still always access the object through a strong reference -- the weak-strong dance.

like image 93
Jody Hagins Avatar answered May 04 '23 00:05

Jody Hagins