7
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
}
Amanpreet
  • 1,301
  • 3
  • 12
  • 29
trapper
  • 11,716
  • 7
  • 38
  • 82

3 Answers3

18

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.

Manny
  • 6,277
  • 3
  • 31
  • 45
  • 1
    Manny do you have a link to docs or an article that backs this up: "UIAlertController has been designed to release everything after it has been executed, provided you don't hold a strong reference to it." – alexisSchreier Apr 30 '19 at 07:17
  • @alexisSchreier it has been standard practice (for close to 40 years) for views (and their controller) to be released when removed from the view hierarchy. Also, Manny tested it and you can easily test yourself if you want to verify it. Probably there's some WWDC session or 30 year old documentation for it, but finding it would be difficult. A strong reference to self is the default because most of the time the code requires self to work properly. Weak references should only be used when absolutely necessary to avoid a retain cycle. – Abhi Beckert Jan 19 '21 at 00:35
1

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;

Tushar
  • 3,022
  • 2
  • 26
  • 26
  • I know how to prevent the cycle with a weak reference. I'm asking why UIAlertController doesn't just nil out the blocks when it is done. – trapper Jun 01 '17 at 06:17
1

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];
Ajjjjjjjj
  • 669
  • 4
  • 12
  • I know how to prevent the cycle with a weak reference. I'm asking why UIAlertController doesn't just nil out the blocks when it is done. – trapper Jun 01 '17 at 06:17
  • i also said, strong variable create a separate copy of itself in block. that does not release memory. So, always use weak references. – Ajjjjjjjj Jun 01 '17 at 06:30
  • Yes but once the block has run it could be released, like with dispatch queues. (you can use self safely in dispatch queue blocks) – trapper Jun 02 '17 at 02:29