2

I'm creating a wrapper for UIAlertView (I know about UIAlertController and about several already existing wrappers, it's also for educational purposes).

Suppose it looks like this (very shortened version):

@interface MYAlertView : NSObject
-(void)show;
@end

@interface MYAlertView()<UIAlertViewDelegate>
@end

@implementation MYAlertView
-(void)show {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Some title"
                                                        message:@"Some message"
                                                       delegate:self 
                                              cancelButtonTitle:@"Cancel"
                                              otherButtonTitles:nil];

    [alertView show]
}

#pragma mark UIAlertViewDelegate implementation

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    //Do something.
}
@end

And, for instance, I use it like this:

// USAGE (inside some ViewController)
-(void)showMyAlert {
    dispatch_async(dispatch_get_main_queue(), ^{
        MYAlertView *myAlertView = [[MYAlertView alloc] init];
        [myAlertView show];
    });
}

The problem I have is the following:

  1. [myAlertView show] causes the alertView to appear. myAlertView is set as a delegate of the alertView.
  2. There is the only strong reference to myAlertView: inside the block in the showMyAlert method. When it's finished, myAlertView is deallocated.
  3. When the user clicks a button on the alertView, the alertView calls it's delegate method, but the delegate (myAlertView) is deallocated already, so it causes BAD_ACCESS (the delegate in UIAlertView is declared as assign, not weak).

I want to make MYAlertView as easy to use as it is with UIAlertView, so I don't want to make the user store a strong reference to it somewhere (it is inconvenient).

So, I have to keep the myAlertView alive as long as the alertView is shown somehow. The problem is I can't think of any way other than creating a strong reference inside MyAlertView, assigning it to self, when I show the alertView, and assigning it to nil, when I dismiss it.

Like so (only the changed bits):

@interface MYAlertView()<UIAlertViewDelegate>
//ADDED:
@property (nonatomic, strong) id strongSelfReference;
@end

@implementation MYAlertView
-(void)show {
    UIAlertView *alertView = [[UIAlertView alloc] init /*shortened*/];
    [alertView show]

    //ADDED:
    self.strongSelfReference = self;
}

#pragma mark UIAlertViewDelegate implementation
//ADDED:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    self.strongSelfReference = nil;
}
@end

It should work: the moment the alertView is dismissed, the strongSelfReference will be set to nil, there will be no strong references left to the myAlertView, and it will get deallocated (in theory).

But keeping a strong reference to self like this looks evil to me. Is there a better way?

UPDATE: The MYAlertView in reality is an abstraction layer around the now deprecated UIAlertView and a new UIAlertController (iOS 8+), so subclassing UIAlertView is not an option.

FreeNickname
  • 7,398
  • 2
  • 30
  • 60
  • 1
    Another option is to set `self` as an [associated object](http://nshipster.com/associated-objects/) of the `alertView`. That will keep `MYAlertView` alive at least as long as `UIAlertView` is alive. But what you're currently doing is also fine. – Darren Feb 09 '15 at 21:13
  • @Darren, thank you for your reply! I thought about a similar approach, but I don't think it is reliable enough, since I don't control the `UIAlertView` implementation. If Apple for some reason keeps the `alertView` alive inherently (if they decided to reuse it, for instance), `myAlertView` will leak. It might be specified in the docs, but I haven't seen. I'll go with a self-reference :) – FreeNickname Feb 09 '15 at 21:16

3 Answers3

9

Yes, your object should keep a strong reference to itself. It's not evil to do so.

A self-reference (or, in general, any reference cycle) is not inherently evil. The evil comes in creating one unintentionally such that it is never broken, and thus leaks objects. You're not doing that.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
1

I feel like the answer here is to actually implement MYAlertView as a subclass of UIAlertView instead of an object that floats in the ether. It will stick around as long as your internal UIAlertView would have regularly stuck around.

@interface MYAlertView : UIAlertView
@end

@implementation MYAlertView
- (instancetype)init {
  if (self = [super initWithTitle:@"Some title"
                          message:@"Some message"
                         delegate:self 
                cancelButtonTitle:@"Cancel"
                otherButtonTitles:nil]) {
    // Other setup?
  }
  return self;
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
  // Respond.
}
@end

Update: You should instead create an iOS7 analogy for UIAlertViewController.

@interface MyAlertViewController : UIViewController <UIAlertViewDelegate>
+ (id)makeMeOne;
@end

@implementation MyAlertViewController
- (void)viewDidAppear:(BOOL)animated {
  UIAlertView *alert = [[UIAlertView alloc] init..];
  [alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
  // Respond.
  [self.navigationController popViewControllerAnimated:NO];
}
+ (id)makeMeOne {
  if (iOS7) {
    return [[self alloc] init];
  } else {
    return [[UIAlertViewController alloc] init];
  }
}
@end

Fill in the blanks for setup.

Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51
  • Hello and thanks for your answer! Unfortunately, `MYAlertView` is a bit more complicated. In truth, it is an abstraction layer around both `UIAlertView` (iOS 7) and `UIAlertController` (iOS 8+), so subclassing `UIAlertView` is not an option, unfortunately. I'll edit my question now. Still, +1 to you :) – FreeNickname Feb 09 '15 at 20:27
  • Actually, I gave it a thought, and creating a `UIAlertView` subclass could actually be a way to go. If I made it a separate class, and created it (instead of a usual `UIAlertView` in `MYAlertView`). As for `MyAlertViewController`, it seems to me, it would be unnecessarily hard to do. I don't know (at least, at the moment) how to present it the way `UIAlertController` presents itself. So, I guess, I'll go with the strong self reference :) Still, thank you for your effort! – FreeNickname Feb 09 '15 at 21:11
  • Also, Apple says "The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified." – davidf2281 Feb 10 '15 at 12:46
0

In my opinion, this is an indicator of a bad design.

If you are creating a wrapper for both iOS version's you would be better off exposing a delegate for MYAlertView that mirrors the relevant dismiss actions (or else you won't be able to act on the callbacks and further than this wrapper).

If you are keeping a strong reference to yourself without adding any actual value to the class you are wrapping, maybe it would be better to write a wrapper that accepts a block to callback on completion? At least this way you can monitor the user's actions and let the caller dictate how long the alert is relevant.

After all, if you pass on the delegation then the problem is solved in a readable manner?

davbryn
  • 7,156
  • 2
  • 24
  • 47
  • Thank you for your answer! I wrote the code above specifically for the question (the full version is unnecessarily long for SO). The real class is initialized in a way similar to the `UIAlertController`. It accepts callbacks (on both iOS 7 and iOS 8) and supports some iOS 8-specific stuff (like styles for buttons). The iOS 8-related bit works fine, but I had the aforementioned problem with iOS 7. Hence, my question. – FreeNickname Feb 09 '15 at 21:03