Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does UIAlertController create a retain cycle with self?

UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"action" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    [self doSomething];
}];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];

I understand the cycle. self retains the UIAlertController, UIAlertController retains the UIAlertAction, UIAlertAction retains self

What I mean is internally couldn't this class have been designed to release everything after one of the UIAlertActions has been run?

-

To clarify, I know that this issue can be avoided by using a weak reference to self.

What I am asking is why doesn't UIAlertController just nil out all the actions (and hence their handler blocks) once an action has been selected by the user. This would break the cycle and avoid the whole weakself dance we need to do.

Something like this...

@implementation UIAlertController

...

// An action button was pressed
- (void)actionSelectedIndex:(NSInteger)index
{
    UIAlertAction *action = self.actions[index];
    action.handler(action); // Run the action handler block
    self.actions = nil; // Release all the actions
}
like image 789
trapper Avatar asked Jun 01 '17 03:06

trapper


3 Answers

The question and answers here baffled me, and this is on top of my search results so I'd like to set the record straight:

There is no retain cycle in the example, so there is no need to create a "weak self" in this case. The only time that there's a retain cycle is if self has a strong reference on alert.

UIAlertController has been designed to release everything after it has been executed, provided you don't hold a strong reference to it.

I tried the example in a sample class, and dealloc was successfully called on pop of the controller.

To summarise by example:

UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"action" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    //No retain cycle, no need for weak self
    [self doSomething];
}];
[alert addAction:action];
[self presentViewController:alert animated:YES completion:nil];

vs

//assume this is a strong reference
self.alert = [UIAlertController alertControllerWithTitle:@"alert" message:nil preferredStyle:UIAlertControllerStyleAlert];
__weak __typeof(self)weakSelf = self;
UIAlertAction *action = [UIAlertAction actionWithTitle:@"action" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    //Need a weakSelf to avoid retain cycle
    [weakSelf doSomething];
}];
[self.alert addAction:action];
[self presentViewController:self.alert animated:YES completion:nil];

Side note: It's wrong to assume that you always need to use weak reference to self when putting it in blocks.

like image 97
Manny Avatar answered Nov 13 '22 07:11

Manny


The issue is not how UIAlertController is designed, but how block works i.e it captures/retains the reference object unless the reference variable is marked weak. I think changing the block code from [self doSomething]; to [weakSelf doSomething]; should fix the retain cycle. Where the weakSelf variable can be declared before the action creation like below:

__weak YourViewCOntrollerClass *weakSelf = self;

like image 1
Tushar Avatar answered Nov 13 '22 07:11

Tushar


Actually when we use strong instance in block( like you are using self ), it create the separate copy increase the retain count. After that class decrement the retain count by calling dealloc method. But cannot make it zero. weak reference release the count after its use. so, create weak reference like this:

   __weak __typeof(self)weakSelf = self;

 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"alert" message:nil preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"action" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [weakSelf doSomething];
    }];
    [alert addAction:action];
    [self presentViewController:alert animated:YES completion:nil];
like image 1
Ajjjjjjjj Avatar answered Nov 13 '22 08:11

Ajjjjjjjj