Andrew Hershberger
Andrew Hershberger

Reputation: 4162

UIApplicationWillEnterForegroundNotification received after applicationState != UIApplicationStateBackground test passed

I am writing an Objective-C wrapper for using a GCD timer source. One of the goals is for the wrapper to manage suspending the timer source when the application enters the background.

The docs for -[UIApplicationDelegate applicationDidEnterBackground:] indicate that it should be used to invalidate timers, among other things; I'm interpreting this somewhat loosely (perhaps that is the source of my problem?) in that I'm suspending a GCD timer (dispatch_suspend()) in response to UIApplicationDidEnterBackgroundNotification).

When an instance of the wrapper is initialized, it checks [UIApplication sharedApplication].applicationState != UIApplicationStateBackground to determine whether the GCD timer source should initially be resumed as part of the initialization sequence. It also registers for UIApplicationDidEnterBackgroundNotification and UIApplicationWillEnterForegroundNotification which it uses to suspend and resume the timer source (respectively).

The issue that I have observed is that there seem to be cases where the check of [UIApplication sharedApplication].applicationState != UIApplicationStateBackground during initialization is passed so that the timer source is resumed, and the next notification that is received is UIApplicationWillEnterForegroundNotification which causes the timer source to be resumed a second time. This leads to a crash since the timer was not suspended at the time of the second resume.

I can work around this by keeping track of the application state locally and confirming the transitions that would be duplicate, but I am concerned that I may be doing something incorrectly or that there may be a bug (either in implementation or in documentation).

Upvotes: 4

Views: 2102

Answers (3)

Miles Alden
Miles Alden

Reputation: 1540

So here's something you can do to get a better picture of what's going on:

In your apps main.m, add this:

 @interface GTTestObject : NSObject

 - (void)logNotification:(id)sender;
 @end

 @implementation GTTestObject

 - (void)logNotification:(id)sender {

     NSLog(@"%@", [(NSNotification *)sender name]);

 }
 @end


 int main(int argc, char *argv[])
 {
     @autoreleasepool {

         // I'm assuming you'd be using ARC...
         GTTestObject *obj = [[GTTestObject alloc] init];
         [[NSNotificationCenter defaultCenter] addObserver:obj selector:@selector(logNotification:) name:nil object:nil];

         return UIApplicationMain(argc, argv, nil, NSStringFromClass([GTAppDelegate class]));
     }
 }

Then for every MyAppDelegate.m method "...didFinishLaunching, ...didEnterBackground, etc." add this:

     NSLog(@"[%@ %@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));

Now you'll be able to see the sequence, time-stamp and (possibly) any conflicts with notification/delegate calls occurring simultaneously.

It's possible you'll get slightly different results from test to test, so run it several times to be sure.

For an example run, I see this every time:

 2012-12-21 08:13:58.559 Gamerton[34158:c07] [GTAppDelegate application:didFinishLaunchingWithOptions:] state: UIApplicationStateInactive
 2012-12-21 08:13:58.560 Gamerton[34158:c07] UIApplicationDidFinishLaunchingNotification state: UIApplicationStateInactive

 ...

 2012-12-21 08:13:58.561 Gamerton[34158:c07] [GTAppDelegate applicationDidBecomeActive:] state: UIApplicationStateActive
 2012-12-21 08:13:58.561 Gamerton[34158:c07] UIApplicationDidBecomeActiveNotification state: UIApplicationStateActive

 ... Hit home button

 2012-12-21 08:16:08.227 Gamerton[34170:c07] [GTAppDelegate applicationWillResignActive:] state: UIApplicationStateActive
 2012-12-21 08:16:08.228 Gamerton[34170:c07] UIApplicationWillResignActiveNotification state: UIApplicationStateActive
 2012-12-21 08:16:08.229 Gamerton[34170:c07] UIApplicationSuspendedNotification state: UIApplicationStateBackground
 2012-12-21 08:16:08.229 Gamerton[34170:c07] [GTAppDelegate applicationDidEnterBackground:] state: UIApplicationStateBackground

 ... Reopen app

 2012-12-21 08:16:59.364 Gamerton[34170:c07] [GTAppDelegate applicationWillEnterForeground:] state: UIApplicationStateBackground
 2012-12-21 08:16:59.365 Gamerton[34170:c07] UIApplicationWillEnterForegroundNotification state: UIApplicationStateBackground
 2012-12-21 08:16:59.365 Gamerton[34170:c07] _UIApplicationDidRemoveDeactivationReasonNotification
 2012-12-21 08:16:59.366 Gamerton[34170:c07] [GTAppDelegate applicationDidBecomeActive:] state: UIApplicationStateActive
 2012-12-21 08:16:59.366 Gamerton[34170:c07] UIApplicationDidBecomeActiveNotification state: UIApplicationStateActive
 2012-12-21 08:16:59.366 Gamerton[34170:c07] UIApplicationResumedNotification state: UIApplicationStateActive
 2012-12-21 08:16:08.230 Gamerton[34170:c07] UIApplicationDidEnterBackgroundNotification state: UIApplicationStateActive

Upvotes: 0

Karthik
Karthik

Reputation: 1396

This is how app state works:

On initial Launch,

  1. application:didFinishLaunchingWithOptions
  2. applicationDidBecomeActive

When you hit the home button,

  1. applicationWillResignActive
  2. applicationDidEnterBackground

When you open a backgrounded app,

  1. applicationWillEnterForeground
  2. applicationDidBecomeActive

Opening - (initial launch and opening backgrounded app) - applicationDidBecomeActive gets called - initialize timer here.

Hiding or receiving call, etc - applicationWillResignActive gets called - stop timer here.

Hope this helps.

Upvotes: 2

emrys57
emrys57

Reputation: 6806

From the UIApplication docs:

UIApplicationStateInactive
The application is running in the foreground but is not receiving events. 
This might happen as a result of an interruption or because the application
is transitioning to or from the background.

So it's possible that your app was in Background, will soon be in Foreground, but is currently transitioning.

Upvotes: 0

Related Questions