views:

231

answers:

3

My iPhone application supports a proprietary network protocol using the CocoaAsyncSocket library. I need to be able to send a network message out when my iPhone application is closed. The code that sends the message is getting called from the app delegate, but the application shuts down before the message actually goes out. Is there a way to keep the application alive long enough for the message to go out?

Bruce

+2  A: 

The docs from Apple don't specifically state this, but the sense I get from looking around the Web and from personal experience is that you have about 4 to 5 seconds after the user hits the Home button to shut your app before your application actually terminates. The iPhone OS is controlling this so you can't block the termination to allow your program to finish first. Basically when your time is up, your program is killed.

There may be another solution, though. First I'd confirm that your code is really taking more than 5 seconds to run. Perhaps you can have it run in response to a button tap, and time how long it runs. If it is more than 5 seconds, you probably are running into this time out issue.

You might then find a way to trigger a message to be sent from a server that is always running. You should have enough time to trigger a remote action, which in turn could then take as long as it needs to run.

Or perhaps you could save the vital information to the iPhone file system on exit, and send that message the next time someone starts the application, which should theoretically give you enough time.

Hope this helps!

Ken Pespisa
+1  A: 

I assume you're already calling it from your AppDelegate's:

- (void)applicationWillTerminate:(UIApplication *)application

But as you've discovered there's no guarantee it'll be called or will be allowed to finish. There are a few options that may or may not work depending on what you're trying to do:

  • If you need the server to perform some sort of cleaning operation triggered by when the client app is gone then you could try watching for TCP socket closure on the server and treating that as the triggering event. But if you explicitly need to send data back with the closure this may not work.

  • If the data you're sending back is not time-sensitive then you can do like most of the analytics libraries do and cache the data (along with a uuid) on the client then try to send it on app closure. If it goes through, you can clear the cache (or do it the next time the app is run). If it doesn't, it's saved and you can send out when the app is run next. On the server, you would use the uuid to avoid duplicate requests.

  • If the material is time-sensitive then your best bet is to implement heartbeat and send periodic updated values to the server. Then when the client app dies the server times out the heartbeat and can use the last received value as the final closing point of data.

In either case, if an explicit closure event is required by your custom protocol then you may want to reconsider using it in a real-life mobile environment where things have to be much more fluid and tolerant of failure.

Ramin
A: 

As others have noted, there's no way to be absolutely certain that you'll be able to send this, but there are approaches to help.

As Ken notes, you do in practice get a few seconds between "willTerminate" and forced termination, so there generally is time to do what you need.

A problem you're almost certainly running into is with CocoaAsyncSocket. When you get the "willTerminate" message, you're on the last run loop of the main thread. So if you block the main thread, and CocoaAsyncSocket is running on the main thread, it'll never get processed. As I recall, CocoaAsyncSocket won't actually send all the data until the next event loop.

One approach, therefore, is to keep pumping the event loop yourself:

- (void)applicationWillTerminate:(UIApplication *)application
{
     // ...Send your message with CocoaAsyncSocket...

     while (! ...test to see if it sent...)
     {
         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
     }
}

I've also looked at putting this work onto a background thread and letting the main thread terminate, in theory letting us go back to Springboard while continuing to run for a few seconds. It's not immediately clear to me whether this will work properly using NSThread (which are detached). Using POSIX threads (which are joinable by default) may work, but probably circumvents any advantages of the background thread. Anyway, it's something to look at if useful. In my apps, we've used the "post next time we launch" approach, since that always works (even if you crash).

Rob Napier