Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent ARC from deallocating something that's not currently being used but will be soon?

I've run into this problem a couple of times and want to know the correct approach to take.

For an example, let's say I'm writing an iPhone app and I want a custom alert view class that uses blocks.

So I write the class, then later on in my code I go:

MyAlertView *alert = [MyAlertView alertWithBlahBlahBlah...];
[alert addButton:@"button" withBlock:^{ ... }];
[alert show];

Somewhere in the alert view class, we have

- (void)addButton:(NSString *)button withBlock:(void (^))block {
    [_blocks setObject:[block copy] forKey:button];
}

- (void)show {
    ... drawing stuff ...
    UIButton *button = ...
    [button addTarget:self selector:@selector(buttonPressed:) ...];
    ...
}

- (void)buttonPressed:(id)sender {
    ((void (^)())[_blocks objectForKey:[sender title]])();
}

So, the alert view now shows up just fine. The problem is, if I tap a button, it attempts to send the buttonPressed: selector to the MyAlertView object that was displayed. The MyAlertView has, however, been removed from the superview at this time. ARC decides that because the alert view is not owned by anyone anymore, it should be deallocated, not knowing that a button needs to message it in the future. This causes a crash when the button is tapped.

What's the right way to keep the alert view in memory? I could make the MyAlertView object a property of the class that's using it, but that's kind of silly (what if I want to show two alerts at once?).

like image 217
Anshu Chimala Avatar asked Jul 25 '12 02:07

Anshu Chimala


3 Answers

If an object were to remain in memory, and you do not have a reference to it, this is known as a memory leak. As I said in my comments, you need to keep some kind of reference to it so that a) it is not deallocated, b) you can send a message to it, and c) you can deallocate it before your class is deallocated.

The most obvious way to do this would be with a property in your class. Since you said that you don't want to do that (maybe you have a lot of them) then another possible solution would be to keep an array of cached objects that you plan on reusing and eventually deallocating.

like image 108
lnafziger Avatar answered Oct 20 '22 00:10

lnafziger


I think you can use performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay to retain the alert view in runloop.

Actually, I just came across an wrapper implementation for UIAlertView using this skill.

Check UIAlertView input wrapper for more detail.

like image 42
Selkie Avatar answered Oct 19 '22 23:10

Selkie


Quite simply, you're breaking the memory management rules. ARC doesn't change the rules, it just automates them. If you need an object to stay alive, it needs to have an owner. Every object in your app's object graph, all the way back to the application delegate, has an owner. It may not be obvious what that owner is (and sometimes the owner may be an autorelease pool), but there is one.

If you want this view to stick around, it needs to be owned by something, even if it's not "currently being used". If it's onscreen, it should be part of the view hierarchy. If it's not, the ideal owner is likely to be the object that created it.

like image 31
jscs Avatar answered Oct 20 '22 00:10

jscs