Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does it make sense to duplicate pointers in order to solve block-based retain-cycles under ARC?

Under ARC, a block is suspected to cause a retain cycle if you're using self inside the block, for example.

I've seen a workaround here, like this: enter image description here

How can this workaround prevent a retain cycle?

weakRequest is just a pointer to the exact same object referenced by request. When ARC modifies the retain count of weakRequest or request, it's affecting the same object.

Then, in the block, there is this strange thing going on:

__strong ASIHTTPRequest *strongRequest = weakRequest;

This is the eqivalent to saying:

ASIHTTPRequest *strongRequest = weakRequest;
[strongRequest retain];

But again: It's one and the same object. Why all these different variable names? They're just pointers!

I never really cared much about blocks and tried to avoid them. But now this made me curious about what everyone is talking about when they say "a block captures the variables". Until today I thought this just means a block will retain every pointer you use which has been defined outside of the scope of the block, meaning that the block just retains whatever object you touch in it's scope.

I did this quick test:

UIView *v = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:v];
v.backgroundColor = [UIColor orangeColor];

NSLog(@"self = %p", self); // 0x6a12a40

[UIView animateWithDuration:1.5 
                      delay:0
                    options:UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     UIViewController *my = self;
                     NSLog(@"my = %p", my); // 0x6a12a40
                     v.frame = CGRectMake(200, 200, 100, 100);
                 }
                 completion:nil];

Like you can see the object itself stays exactly the same. The block does not create a copy. So I can safely assume all the years of C and Objective-C knowledge are still valid:

ASIHTTPRequest *strongRequest = internetRequest;
ASIHTTPRequest *foo = strongRequest;
ASIHTTPRequest *bar = foo;

if (bar == internetRequest) {
    NSLog(@"exact same thing, of course");
}

So what is going on there? How can this resolve a retain count if all that's happening is create different pointers to the same object? Why the extra mile of creating those pointers?

Wouldn't this be totally the same thing?

[request setCompletionBlock:^{
    NSString *respondeString = [request responseString];
    if ([_delegate respondsToSelector:@selector(pingSuccessful:)]) {
        [_delegate pingSuccessful:responseString];
    }
}];

There must be some secret about Objective-C which explains why duplicating pointers solves memory management problems here. It just doesn't make any sense to me.

like image 719
Proud Member Avatar asked Dec 28 '22 07:12

Proud Member


1 Answers

It actually has nothing to do with ARC, but rather how blocks capture variables. The pointer is duplicated so that the variable captured by the block has the correct ownership qualifier.

weakRequest is just a pointer to the exact same object referenced by request. When ARC modifies the retain count of weakRequest or request, it's affecting the same object.

Right, they both point to the same object, but weakRequest has the __unsafe_unretained ownership qualifier, which means that when that variable is captured by the block, its retain count is unchanged.

If request were captured by the block then it would be retained and you would have retain cycle, regardless of whether you're using ARC.

The conversion back to a __strong pointer simply keeps that object alive for the duration that block execution.

like image 91
Darren Avatar answered Apr 05 '23 23:04

Darren