iOS — GCD what the __weak is going on?

Michael Mavris
Rock and Null
Published in
6 min readJul 29, 2016

--

We all know that a reference to self in a block should always be done using __weak and not direct to self. Even Xcode warns you that this will cause a retail cycle. So that’s clear and nobody can argue about it.

Same for GCD which is using Blocks

Since GCD is using blocks then we need to reference to self as __weak in order to avoid retain cycles. You can read this article explaining why you have to use __weak in order to avoid retain cycles in GCD.

Or not?

If you read the comments of the linked article you will notice that some iOS developers are arguing that GCD won’t cause any retain cycles. You can have a look at this article explaining why GCD won’t cause retain cycles.

What the __weak is going on?

So I was wondering who is wrong and who is right? Using self without __weak reference will cause a leak in GCD or not?

Lets find out

There is no better way to know for sure than trying it by yourself.

The project

The experimental project is a simple view with 2 buttons. The buttons are presenting 2 identical views with an IBAction that performs the same actions with GCD. The one is causing retain cycle and the other not.

The GCD that won’t cause retain cycle

So this code will start spinning the activity indicator in the main thread then add 2 objects to the proArray in a background thread ,then it will sleep for 10 seconds (simulating some heavy work on the thread) and after it finished it will remove one object from array, print the array and stop the spinning indicator.

What happen if remove the UIViewcontroller from Navigation?

If we press the button to execute the startGCD function and immediately (under 10 seconds) press “back” on NavigationBar what will happen to GCDVC(our UIViewController)?

The expected result

The result is that the GCDVC was deallocated but the Block was executed no matter what. So 10 seconds after removing GCDVC the console printed :

2016–07–28 22:48:15.706 GCDRetainCycleTest[8841:4266904] (null)

Null because self was deallocated at the time that NSLog(@”%@”,weakSelf.proArray) was executed so the proArray was deallocated too.

GCDVC deallocated immediately

When we pressed the “back” button the GCDVC was deallocated immediately. That’s good if we don’t need the self to be alive when the block is executed. For example, loading data from our model in the background thread and present them in the main thread using a UITableView. If the user press back then we don’t care to keep the self alive since as soon as it will present the data the UIViewController will be deallocated.

Direct call to self in GCD

Will this code cause a retain cycle?

No it won’t! This code is calling direct the self without __weak but it won’t cause a retain cycle because there is not self retaining the block.

The result

So this code won’t cause a retain cycle but is the result different from the __weak example? Yes, if we execute startGCD: and then press the “back” we will notice that 10 seconds later the console will print:

2016–07–29 10:02:08.877 GCDRetainCycleTest[1021:22526] ( 2 )

What happened here is that after we removed GCDVC from the NavigationController it wasn’t deallocated immediately like the first case. Instead it was keeping self retained until the block was executed and then it was released.

That’s not bad

This case might be useful when we want to make sure that, when the block is executed the self won’t be nil and it will perform as expected. For example, if we have a controller that is updating our Model and the controller will be deallocated in the middle of the process, it would be wise to avoid that by keeping the self around until the process is finished. That way, we guarantee that the block will update the model no matter what.

So GCD won’t cause retain cycles

No it won’t cause retain cycles if you call direct the self like the example. It would just keep self around until the block is executed.

But

Lets see another an example for the GCDVC2 without using __weak

It’s obvious

This code it’s exactly the same with the previous example but instead of using Blocks inside the GCD we just store them in a @property. There is no need to run this code since Xcode will warn you that your code is likely to lead to a retain cycle.

But you told me that GCD won’t cause retain cycles

That’s partially true! If you use GCD without storing the blocks in a @property, and call directly the self, then it won’t cause retain cycle. But if you store them in @property, it will cause!

So what’s causing the retain cycle?

self.addObjectsBlock = ^{ 

[self.proArray addObject:@”2"];
[NSThread sleepForTimeInterval:10];
dispatch_async(dispatch_get_main_queue(),self.postGCDBlock);
};

The problem here is that we have a strong reference to the block (self.addObjectsBlock =^{}) retaning the block and then we have a strong reference from block back to self ([self.proArray addObject:@”2"]). That’s the definition of retain cycle!

But I want to store my blocks in properties

Don’t worry, there is a solution for that! This is where __weak make sense in GCD.

Let’s use __weak

Let’s see how to avoid the retain cycle:

That should do it!

With this piece of code we solve the problem of retain cycle but we cause a another one! Can you guess?

weakSelf it’s too weak

This code will crash your app 10 seconds after you pop GCDVC2 from your NavigationController. The reason is that, any other self other than the first one, is not guaranteed that it won’t be nil.

self.addObjectsBlock = ^{ [weakSelf.proArray addObject:@”2"]; //Guarantee that it won't be nil
[weakSelf.proArray addObject:@"3"]; // Not guaranteed
[NSThread sleepForTimeInterval:10]; //Put thread to sleepdispatch_async(dispatch_get_main_queue(),weakSelf.postGCDBlock); //Since we pop our GCDVC2 the call weakSelf.postGCDBlock will crash the app since weakSelf is nil
};

weakSelf it’s too weak, call to self is too strong, now what?

There is a trick that Apple recommends which is capturing the weakSelf in the block with a strongSelf. What that means is that self is retained as long as the block is executed and it’s released as soon as the block completed. It’s actually has the same behaviour with calling direct self in GCD blocks (Second example: GCDRetainCycleTest2.m)

Show me the magic:

Problem solved?

It depends! We solved the problem with the code that was crashing the app but we still have a problem since the result will (probably) be:

2016–07–29 15:52:23.940 GCDRetainCycleTest[1225:41943] (null)

The problem here is that we make sure that strongSelf won’t be nil inside the self.addObjectBlock=^{} but there is no guarantee that it won’t be nil in the self.postGCDBlock =^{}. Since self would probably be nil in the second block we will get null.

If we don’t care about the block that it will be executed in main thread ( self.postGCDBlock =^{}) we can assume that our problem has been solved. But since we need to print the proArray in the main thread we need to solve one more problem.

You guessed it right

We will add on more __strong reference to self in the self.postGCDBlock=^{}.

Now it should work like a charm

OK I was pretty confident that this solution should work as expected. Instead the results was kind of random because there was no guarantee will be around when the self.postGCDBlock =^{} was executed.

The issue lies on this line of code:

dispatch_async(dispatch_get_main_queue(),strongSelf.postGCDBlock);

Passing the strongSelf.postGCDBlock as parameter won’t retain self. Replacing this line of code with the following solves the problem:

dispatch_async(dispatch_get_main_queue(), ^{
strongSelf.postGCDBlock();
});

So the final solution would be:

Who was right?

It seems that both “teams” were right. You can’t cause retain cycle in GCD if you are passing directly the blocks since there is no strong reference back to self but you can cause a retain cycle if you store the blocks in properties.

The moral of the story

The moral of the story it was that reading about an issue will help you understand what’s going on but questioning everything and testing everything to get your answers, will make you better Software Engineer. For example I was aware about __weak and __strong before starting this experiment but I learned that I have to call strongSelf.postGCDBlock() to get the expected result. That’s the difference of theory and practise!

If you enjoyed this article check out our publication Rock n Null about mobile and technology!

You can find the full project here.

--

--

Michael Mavris
Rock and Null

A curious Mobile Software Engineer. Loves ski, football and rock music!