0

When a UIApplicationDelegate is instantiated from:

UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))

Tthe AppDelegate remains in memory even though the [[UIApplication sharedApplication] delegate] property is a weak reference.

However, if you unset and re-set the delegate property, like so:

id originalDelegate = [[UIApplication sharedApplication] delegate];
[[UIApplication sharedApplication] setDelegate:nil];
[[UIApplication sharedApplication] setDelegate:originalDelegate];

then it becomes invalid to use. Any idea why it initially is fine with a weak reference?

David Liu
  • 9,426
  • 5
  • 40
  • 63
  • How are you trying to use it? – quellish Jul 29 '14 at 23:18
  • Making a 3rd party library. I need to temporarily change another UIApplication's delegate to my own (to override some methods while passing through others), or alternatively swizzle some methods, which requires an unset/reset since UIApplication does a one-time check whether the delegate methods exist or not upon setting. More curious than anything really though, if there's a better way to keep the original app delegate strong than just having a static variable holding on to it for the rest of the app lifetime. – David Liu Jul 29 '14 at 23:32
  • @DavidLiu So if calling `setDelegate:` invalidates the cache, could you just call it without setting the delegate to `nil` first, or does it check to see if it's the same instance? – jlehr Jul 29 '14 at 23:55
  • @DavidLiu Is it possible you could you accomplish what you need by registering for some of the many notifications posted by the application object, rather than by getting messaged directly? – jlehr Jul 30 '14 at 00:01
  • @jlehr Unfortunately, no. The delegate method being overridden has a return value that would need to change. – David Liu Jul 30 '14 at 00:17

3 Answers3

1

It gets deallocated when that pointer to it is set to nil, along with the app's window. Keep a strong pointer to it and it will stay around...

@property(strong, nonatomic) AppDelegate *strongAppDelegate;

self.strongAppDelegate = [[UIApplication sharedApplication] delegate];
[[UIApplication sharedApplication] setDelegate:nil];
[[UIApplication sharedApplication] setDelegate:self.strongAppDelegate];

This can be proven by implementing dealloc in your AppDelegate and NSLogging there.

EDIT - Anyway, reading how you intend to use it, consider two alternatives:

  1. add methods to your api to be called from the client's app delegate hooks
  2. (even better) subscribe to the UIApplication's state changes via NSNotificationCenter (e.g. see the doc under "Notifications". It posts things like UIApplicationDidBecomeActiveNotification).

Getting in between your customer's app and the delegate hooks (even for just a moment) seems like a compromise most wouldn't like.

danh
  • 62,181
  • 10
  • 95
  • 136
  • I guess the question I was wondering was why it's initially fine in the first place. If UIApplication is only keeping a weak reference to the delegate, what's keeping it alive in the beginning? I realize I could just hold onto it with a long running static variable, but as a 3rd party library, that seems a little sketchy. – David Liu Jul 29 '14 at 23:37
  • I think what's going on is that it's being allocated UIApplicationMain, so it's +1 retain count when assigned to the sharedApplication. The weak ref as there doesn't add to the retain count, but setting to nil follows the regular rules. It gets dealloc'd and then it's gone for good. – danh Jul 29 '14 at 23:42
  • @danh This answer shouldn't be needed. In the OP's code, `originalDelegate` is a strong reference. – rmaddy Jul 29 '14 at 23:46
  • @rmaddy what makes you think that? It looks like a local variable to me. – danh Jul 29 '14 at 23:59
  • @danh Yes, it's local and local variables are `strong` by default. – rmaddy Jul 30 '14 at 00:00
  • @rmaddy - try the code each way, once with an id on the stack and the other with a strong property. NSLog in the dealloc. Let me know if you see different behavior (and why). – danh Jul 30 '14 at 00:16
  • @maddy Just to be clear, I'm referring to the behavior of the weak property after the local variable goes out of scope. While you're correct that the strong local reference would keep it alive, it would only last the local scope. – David Liu Jul 30 '14 at 00:20
  • @DavidLiu Yes, you are correct. If the delegate is weak, and there are no other strong references, then when the local variable goes out of scope, the delegate will be deallocated. – rmaddy Jul 30 '14 at 01:33
0

Of course the implementation is opaque and might conceivably change in the future, but it would seem as though the framework is making an extra call to retain at launch time, and later balances that by sending release in setDelegate:. You might have to store the original delegate instance in a property somewhere. I suppose if you wanted to be extra careful about potential leaks, you could try using KVO to observe changes to the application object's delegate property so that you could make the corresponding change to the extra property.

It might still be a little dicey though, since we don't know what else may be affected behind the scenes. (For example, the app delegate probably needs to register to receive certain notifications, etc.) That's unfortunate, because switching the app delegate instance at runtime doesn't seem to me to be an unreasonable thing to want to do.

Edit

If at all possible, see if you can accomplish what you need by registering for some of the notifications posted by the UIApplication instance -- they're pretty comprehensive. The approach you've been exploring is unfortunately quite risky.

jlehr
  • 15,557
  • 5
  • 43
  • 45
  • Yeah, it's all very very dicey in general. The idea of KVO to watch the UIApplication delegate property seems very useful though to take care of any potential leaks (which is my biggest worry about the approach). – David Liu Jul 30 '14 at 00:23
-2
id originalDelegate = [[UIApplication sharedApplication] delegate]; //weak reference point to delegate. delegate is a weak property
[[UIApplication sharedApplication] setDelegate:nil]; // delegate point to nil, originalDelegate was pointing to delegate so now originalDelegate points to nil as well.
[[UIApplication sharedApplication] setDelegate:originalDelegate]; // delegate points to originalDelegate which was pointing already to nil.
artud2000
  • 544
  • 4
  • 9
  • The OP is compiling with ARC enabled. The default lifetime qualifier for local variables is `__strong`, so `originalDelegate` still has a retain count of at least 1 until the end of the local scope. – jlehr Jul 29 '14 at 23:37
  • it's actually true, your [UIApplication sharedApplication] has a delegate property, that can be accessed as [[UIApplication sharedApplication] delegate] or [UIApplication sharedApplication].delegate when you use the setter you are setting the iVar through the accessor to nil and originalDelegate was pointing already to that property called delegate. Which is not more than the accessors that allow you to use the dot operator – artud2000 Jul 29 '14 at 23:53
  • When you call `[[UIApplication sharedApplication] setDelegate:nil]` you are changing the reference UIApplication has of the delegate to point to nil, you are not setting the delegate itself to nil, I think your confusion is there. – pedros Jul 30 '14 at 00:37
  • But UIapplication has a property named delegate. When you invoke setDelegate you are using the accessor that looks in a very simple implementation like. Check the documentation about properties in iOS and synthesize - (void)setDelegate:(id)something{ _delegate = something } – artud2000 Jul 30 '14 at 17:41
  • You should now that a property is no more than the capability to use dot operator to access the iVar. [UIApplication sharedApplication].delegate = something is equivalent to [[UIApplication sharedApplication] setDelegate:something] – artud2000 Jul 30 '14 at 17:45