When to call completionHandler in application:performFetchWithCompletionHandler: when Background Fetch is async?
Asked Answered
N

1

6

I have an app that fetches content in the background with the help of Background Fetch.

So if a Background Fetch should take place my application:performFetchWithCompletionHandler: method is called. In this method I use NSURLConnection to fetch content asynchronous.

In my current implementation I only start the request and then call the completionHandler with UIBackgroundFetchResultNewData. I know that this cannot be right. So my question is, how do I correctly call the completionHandler when the async request finishes in connection:didReceiveData: method.

Nikolaus answered 16/10, 2013 at 21:33 Comment(0)
N
15

You're right — you should call the completion handler only when your fetch is actually complete. Otherwise iOS will probably put your application back to sleep before the connection completes, and apps shouldn't actually be able to determine UIBackgroundFetchResultNewData versus UIBackgroundFetchResultNoData or UIBackgroundFetchResultFailed until then anyway. How do you know your connection will succeed?

You need to keep hold of the completionHandler and call it only once the connection has finished. Most likely you'll want to add an instance variable to your delegate, copy the completion handler into there, and call it when you're done.

Aside: connection:didReceiveData: doesn't signal the end of a request. Per the documentation:

Sent as a connection loads data incrementally.

[...]

The delegate should concatenate the contents of each data object delivered to build up the complete data for a URL load.

You may receive any number of calls and the net result of the URL connection is the accumulation of all of them.

EDIT: you store a block by creating an instance variable of the correct type and copying the block to it. Blocks have unusual semantics because, unlike every other kind of Objective-C object, they're initially created on the stack. The net effect is just that you always copy them. If they're on the stack when you copy then they end up on the heap. If they're already on the heap then the copy just acts as a retain, since blocks are always immutable anyway.

So:

@implementation XXMDYourClass
{
    // syntax follow the C rule; read from the centre outwards
    void (^_completionHandler)(UIBackgroundFetchResult);
}

- (id)initWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    self = [super init];

    if(self)
    {
        // keep the block by copying it; release later if
        // you're not using ARC
        _completionHandler = [completionHandler copy];
    }

    return self;
}

- (void)somethingThatHappensMuchLater
{
     _completionHandler(UIBackgroundFetchResultWhatever);
}

@end
Northward answered 16/10, 2013 at 21:57 Comment(2)
this is already very close, unfortunately I'm unable to find out how to store the completionHandler as I'm not familiar with blocks. I would be glad if you could point that out. thanksNikolaus
When I call completion handler asynchronously then I get warning Warning: Application delegate received call to -application:performFetchWithCompletionHandler: but the completion handler was never called.. What I can do with this? It would be better to not wait in this method for asynchronous operation.Decorticate

© 2022 - 2024 — McMap. All rights reserved.