I'm wrapping an UIAlertView inside a regular NSObject to allow completion handler blocks instead of the delegate pattern.
The problem is that I allocate a local instance of my object, that internally creates an UIAlertView and assigns its delegate to the object itself. When the alert is shown and the user taps a button, the apps crashes with an EXC_BAD_ACCESS because ARC has released my object and the delegate of the alert is that object.
How could I handle this situation? I saw that a solution is to qualify the local variable with __block and use the object itself inside the completion block, but that doesn't work.
By the way, if I subclass 'UIalertView' instead of wrapping it, it works, but documentation says that alert subclassing is not recommended, so I prefer to resolve this issue.
You can associate your object with the alert view like so:
#import <objc/runtime.h>
...
- (void)showAlertView
{
UIAlertView *alertView = [[UIAlertView alloc] initWithWhatever:...];
objc_setAssociatedObject(alertView, _cmd, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[alertView show];
}
That will retain your object, and then release it again when the alertView is dealloced. Your object mustn't retain the alertView, or you'll have a retain cycle.
Related
I have a viewcontroller class and another class of NSObject. I call from the viewcontroller class with the following method the NSObject class.
SubmitContentViewController class
#implementation SubmitContentViewController
-(void)viewDidLoad{
[self callUploadQueueClass];
}
-(void)callUploadQueueClass{
UploadQueueClass *queue = [UploadQueueClass new];
[self generateIDforImage];
}
#end
UploadQueueClass
#implementation UploadQueueClass
-(void)generateIDforImage{
#weakify(self)
[[[ApiServicesProvider shared] userService] getCreatorsContentID:^(NSDictionary * _Nullable result, NSError * _Nullable error) {
#strongify(self)
if(nil==error){
NSString* ccID = result.creatorsContentId;
self.creatorsContentID = ccID;
NSLog(#"creatorsContentID %#",self.creatorsContentID);
[self getImageUploadURL:ccID withNumberOfAttempts:10];
}
else{
self.isUploading = NO;
}
}];
}
#end
at this line
NSLog(#"creatorsContentID %#",self.creatorsContentID);
creatorsContentID is null although at this line
self.creatorsContentID = ccID;
ccID is not null so self.creatorsContentID should not be null.
Moreover at this line
[self getImageUploadURL:ccID withNumberOfAttempts:10];
is never get called.
What am i missing?
You are creating your UploadQueueClass instance as a local variable *queue in callUploadQueueClass.
This local variable holds a strong reference to the UploadQueueClass instance.
As soon as the function returns, that local variable is released and it no longer holds the strong reference.
This happens before getCreatorsContentID has completed its work. Before the completion handler block is called.
You have used #weakify so that the self captured by the block does not hold a strong reference to the UploadQueueClass instance. The local variable has been released and the block self doesn't hold a strong reference to the instance. Nothing does, so it is released.
The self in the block is now nil. Using #Strongify won't help you here; the object has already gone away;
In this case you don't need to use #weakify; There is no danger of a circular reference causing a memory leak; The blocks capture of self only lasts until the completion handler has done its work.
However, removing #weakify doesn't seem like it would really help since there doesn't seem to be any way for the UploadQueueClass instance to communicate its results back to the calling view controller.
It would be more typical for the view controller to provide the completion handler block to the function it is calling, or at least provide some block to be executed. This is where you could use #weakify since the view controller instance would be self, but the block doesn't need to hold a strong reference to it to keep it around; The view controller hierarchy is doing that.
Since you don't want this object to report back to the view controller, simply remove the #weakify/#strongify. Then the block itself will hold a strong reference to the UploadQueueClass instance until it returns and then the object will be released by ARC.
In my non-ARC iOS code, I use the following pattern: a delegate proxy class that forwards a single delegate message to some other class, then releases self. Here's an example for UIAlertView:
#interface AlertCallback : NSObject
<UIAlertViewDelegate>
{
NSObject *m_Sink;
SEL m_SinkSel;
}
-(id)initWithSink:(id)Sink SinkSel:(SEL)Sel;
#end
#implementation AlertCallback
-(id)initWithSink:(id)Sink SinkSel:(SEL)Sel
{
if(self = [super init])
{
m_Sink = Sink;
m_SinkSel = Sel;
}
return self;
}
- (void)alertView:(UIAlertView *)av didDismissWithButtonIndex:(NSInteger)n
{
//Call the callback
[m_Sink performSelector:m_SinkSel withObject:#(n)];
[self autorelease];
}
#end
//And finally usage:
AlertCallback *del =
[[AlertCallback alloc]
initWithSink:self
SinkSel:#selector(OnIAmSure:)];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:nil
message:#"Are you sure?"
delegate: del
cancelButtonTitle:#"No"
otherButtonTitles: #"Yes", nil];
The idea here is that the proxy object will stay alive until the user taps a button, at which point the proxy will invoke its host's method and commit suicide. I'm using a similar pattern for action sheet and connections.
This doesn't translate to ARC. The delegate on the UIAlertView is weak. With only a weak ref to it, the AlertCallback with be released right away.
I can see several ways to overcome this. The callback can hold a reference to self (a deliberate ref loop) and nil it when the delegate message comes. It's also possible to derive a class from UIAlertView, implement the delegate protocol, and make it designate self as the delegate - but overriding the init method would be tricky; I don't know how to override a variadic method, passing an unknown number of parameters to the superclass. Finally, I could build a category on top of UIAlertView, specifying self as delegate, and use objc_setAssociatedObject for extra data items. Clunky, but it might work.
Is there a preferred/recommended way to implement this pattern under ARC?
Your first solution, keeping a self reference, works fine - see for example Manual object lifetime with ARC.
If you cannot, or do not wish to, modify the class to manage its own lifetime then a standard solution is to use associated objects. This is a standard runtime feature which effectively allows the lifetime of one object to be linked to that of another. In your case you can associate your delegate to your UIAlertView, effectively making the delegate reference strong rather than weak. Many questions on SO deal with associated objects, for example see Is it ever OK to have a 'strong' reference to a delegate?.
HTH
I wanted to understand the following scenario
-(void) foo
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Could not save file"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
//This is called when ok is pressed
}
In the above code a UIAlertView strong pointer is created and a delegate to self is assigned. The reason I called it a strong reference pointer is because it is created in scope and will go out of scope when its reference count goes to 0. I believe the reference count goes to 0 when the method foo ends then why am I still getting a callback at clickedButtonAtIndex ? I was under the assumption that we would not get a callback because the destructor of the alertView instance would have been called as soon as the method foo ended.
I believe the reference count goes to 0
You believe wrong. When you say [alert show], you hand the alert object over to Cocoa, which retains it; otherwise there would be no alert view to appear on the screen! That alert view has a reference (which is actually weak) to you (self). And Cocoa thus is able to hand the very same alert view back to you in the delegate callback; the alert is still alive because Cocoa is still retaining it, and you are still alive because you are still alive, so the reference to self works as the target of the callback.
Also, I can't quite tell whether you grasp that as soon as you say [alert show], the code does not pause - it goes right on, immediately. Thus the first method is over before the alert actually appears on the screen. Again, this works because the alert has been handed over to Cocoa, which retains it and takes care of showing it on the next runloop. None of your code is running while the alert is present on the screen.
A completely parallel situation is
MyViewController* vc = [MyViewController new];
[self.presentViewController:vc animated:YES completion:nil];
The code ends, so why doesn't MyViewController vanish in a puff of smoke? Because presentViewController hands it over to Cocoa, which inserts it into the view controller hierarchy and retains it.
The object is deallocated in ARC mode and causes crash. My code below;
BlockAlertView* alert = [BlockAlertView alertWithTitle:title message:message];
[alert setCancelButtonWithTitle:NSLocalizedString(#"No", #"No Button") block:nil];
[alert addButtonWithTitle:NSLocalizedString(#"Yes", #"Yes Button") block:^{
//Do Something
}];
[alert show];
It appears the correct alert view(which is custom UIView) but when I click one of the button it crashes.
The crash log;
2015-04-07 22:28:17.065 Test[3213:781570] <BlockAlertView: 0x16bb4160>
2015-04-07 22:33:01.746 Test[3213:781570] *** -[BlockAlertView performSelector:withObject:withObject:]: message sent to deallocated instance 0x16bb4160
Here is the source code of BlockAlertView;
BlockAlertView on Github
For now I can't estimate any of the clues for this and makes me old.
Any input will be much appreciated!
Presumably, that code worked correctly before it was converted to ARC.
To fix it, you'll need to create a strong reference to self in the -show method, and release this reference in -dismissWithClickedButtonIndex:animated: (where you see [self autorelease] commented out).
You can do this with a simple instance variable:
id _selfReference;
Assign self to _selfReference in -show:
- (void)show
{
_selfReference = self;
...
and then set _selfReference to nil in -dismissWithClickedButtonIndex:animated: in the two places where you see [self autorelease] commented out.
Assign your alert object to live somewhere beyond your current function. One simple possibility is to just make it an instance variable. If that's not practical, create an instance NSMutableArray *retainedItems; which you allocate/init, and stuff this into.
Looks like a design flaw in that project. The class is poorly named BlockAlertView as it's not actually a subclass of UIView. If it were a view and it was added to the view hierarchy then the view hierarchy would ensure it stayed alive whilst being viewed. As it is the view is kept alive but the object that created the view BlockAlertView is not held onto by anything and by the time the actions are called BlockAlertView is long gone.
This will require you to keep a strong ivar around to reference this "controller" object and it would be sensible to nil out that ivar in the completion blocks.
BlockAlertView *alertController = [BlockAlertView alertWithTitle:title message:message]; {
[alertController setCancelButtonWithTitle:NSLocalizedString(#"No", #"No Button") block:nil];
__weak __typeof(self) weakSelf = self;
[alertController addButtonWithTitle:NSLocalizedString(#"Yes", #"Yes Button") block:^{
//Do Something
weakSelf.alertController = nil;
}];
[alertController show];
}
self.alertController = alertController;
I have few viewControllers who use some alertbox, instead of having a delegate in every controller, i would like to create a class like "alertboxDelegate" for that, and link all my alertview to this delegate.
How can i do that?
Thank you
Depending on what you use your alert views for, it probably doesn't make sense to put all the delegate behavior for the whole app in a single class. Before you do this, you should make sure you're following single responsibility principle.
If you're sure you want to, you'd need to define a class and have it implement UIAlertViewDelegate.
// AlertViewDelegate.h
#interface AlertViewDelegate <UIAlertViewDelegate>
#end
// AlertViewDelegate.m
#implementation AlertViewDelegate
#end
In your view controller where you're presenting an alert view, you'll need to create an instance of this class, but it also has to be retained. The alert view itself won't do this, since delegates are weak references. You can use Objective-C associated objects to retain it, which will cause the delegate to be released when the alert view itself is released.
- (void)presentAlert
{
AlertViewDelegate delegate = [[AlertViewDelegate alloc] init];
UIAlertView *alert = [UIAlertView ...];
alert.delegate = delegate;
objc_setAssociatedObject(alert, "RetainedDelegate", delegate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[alert show];
}
I would strongly recommend against using this pattern, though, and implementing the delegate in the presenting view controller.