Why does it make sense to duplicate pointers to allow circular save cycles in ARC?

In ARC, it is assumed that a block causes a save loop if you use self inside a block, for example.

I saw a workaround here , for example: enter image description here

How does this workaround prevent a save cycle?

weakRequest is just a pointer to the same object that request refers to. When ARC changes the persistence value of weakRequest or request , it affects the same object.

Then in the block this strange thing happens:

 __strong ASIHTTPRequest *strongRequest = weakRequest; 

It means that:

 ASIHTTPRequest *strongRequest = weakRequest; [strongRequest retain]; 

But again: this is one and the same object. Why are all these different variable names? They are just pointers!

I never cared about blocks or tried to avoid them. But now it made me admire what everyone is talking about when they say that "the block captures the variables." Until today, I thought that this means that the block will save every pointer that you use that was defined outside the scope of the block, which means that the block just saves any object that you touch in its area.

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]; 

As you can see, the object itself remains the same. The block does not create a copy. Therefore, I can safely assume that all years of knowledge of C and Objective-C remain valid:

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

So what's going on there? How to resolve the save account, if all this happens, creates different pointers to the same object? Why an extra mile of creating these pointers?

Won't it be exactly the same thing?

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

There must be some secret in Objective-C that explains why duplicate pointers solve memory management problems here. It just doesn't make any sense to me.

+4
source share
3 answers

In fact, this has nothing to do with ARC, but rather how to block the capture of variables. The pointer is duplicated, so the variable captured by the block has the correct property qualifier.

weakRequest is just a pointer to the same object the request refers to. When ARC changes the keep of weakRequest or query value, it affects the same object.

That's right, they both point to the same object, but weakRequest has the property qualifier __unsafe_unretained , which means that when this variable is captured by the block, its save count remains unchanged.

If the request was captured by the block, it will be saved and you would save the loop, regardless of whether you use ARC.

Converting back to a __strong pointer simply saves this object for the duration of the lock.

+6
source

Well, you specify the variable as __weak exactly so that the block does not save it, so that you can avoid the save loop. However, creating the __strong Variable inside the block and pointing it to the __weak variable is completely redundant. You call it weak so that the block does not retain it. Creating a new one and marking it as __strong does not mean anything, because there is no instance in which the block will need to save it. __strong is just a compiler keyword to tell ARC to keep the value if the need arrives ... and ARC will never find that need, because it is already passed to the block. In the end, you can simply use the weakRequest variable and destroy the strongRequest variable.

+2
source

You may be confused because there are two different things to prevent two different problems. You quoted this line:

 __strong ASIHTTPRequest *strongRequest = weakRequest; 

This line does not prevent the save cycle.

The conservation cycle (potential) is one problem. The save loop will contain three objects: self , ASIHTTPRequest and a block. Using the weakRequest variable aborts this loop because the block captures the weakRequest , which does not own the ASIHTTPRequest object. In terms of reference count, assigning te weakRequest does not increase the reference count of ASIHTTPRequest .

The line you specified is intended to prevent another problem that is created when solving the first problem. Another problem is a potential dangling pointer. Since weakRequest does not have ASIHTTPRequest , there is a risk that all ASIHTTPRequest owners ASIHTTPRequest free it during execution of the completion block. Then weakRequest will be a dangling pointer - a pointer to an object that has been freed. Any use of it can cause the heap to crash or be damaged.

In the line you specified, the block copies weakRequest to strongRequest . Since strongRequest is __strong , the compiler generates code to save (increase the reference count) ASIHTTPRequest and code to release it at the end of the block. This means that even if all other owners of ASIHTTPRequest release it while the block is ASIHTTPRequest , ASIHTTPRequest will remain alive, since the block temporarily made itself the owner of the request.

Please note that this solution to the dangling pointer problem is not thread safe. If request owners can free it from other threads during block execution, there is a race condition that can still lead to a dangling pointer. This is why you should try to use __weak instead of __unsafe_unretained for weak pointers: __weak links can be copied to __strong links without a race condition.

+2
source

Source: https://habr.com/ru/post/1390918/


All Articles