0

I'm creating an online-shop-style app where users can browse different products on their iPad and order these products. The ordering process consists of creating an xml-file with the user's data and the relevant products he would like to order. But sometimes there might be the case, that users don't have an internet connection right now and I would like to create some mechanism, which checks every x minutes for an active internet connection and then tries to deliver the order-xml. It should repeat this step until it gets connected to the web and then just stop it, when all offline carts have been sent.

I have already been searching the web but only found ways to do this on iOS 7 (with UIBackgroundModes - fetch). But I don't want to use iOS 7 because the app is already done and I'm not planning to redesign it for iOS 7 (it's an Enterprise App). As far as I know, the current Background Execution time on iOS 6 is limited to something like 15 minutes, is that correct?

Any ideas on how to solve that?

Thanks.

EDIT: I have tried the following in - (void)applicationDidEnterBackground:(UIApplication *)application

self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
    [[InstanceHolder getInstance] startNetworkTimer];
}];

and here is what should happen next:

- (void) startNetworkTimer{
    if ([CommonCode getAllOfflineCartsForClient:nil].count > 0){
        NSTimer *pauseTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(offlineCartLoop:) userInfo:nil repeats:YES];
    }

}

- (void) offlineCartLoop:(id)sender{
    if([CommonCode isInternetConnectionAvailable]){
        [self sendOfflineCarts];
        [sender invalidate];
    }
}

startNetworkTimer gets called as it should, but then it doesn't call the offlineCartLoop function :-(

EDIT 2:

I think the timer-thing was the problem. I'm now calling the offlineCartLoop function like this:

self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
    [[InstanceHolder getInstance] offlineCartLoop:nil];
}];

and changed the offlineCartLoop function to this:

- (void) offlineCartLoop:(id)sender{
    if([CommonCode isInternetConnectionAvailable]){
        [self sendOfflineCarts];
    }else{
        [NSThread sleepForTimeInterval:10.0];
        [self offlineCartLoop:nil];
    }
}

Seems to work, but will this run forever? Is there anything else I need to take care of?

gasparuff
  • 2,295
  • 29
  • 48

2 Answers2

2

There is no solution to what you want - there is no such thing as being able to periodically check every N minutes in the background unless it is within the time window granted by beginBackgroundTaskWithExpirationHandler. However that only permits 10 minutes of execution time for iOS6 and earlier, or approximately 3 minutes for iOS7.

You cannot cheat and try and use a background mode if your app does not need it, and even the background modes do not permit you to freely run whenever you want. Even the new background modes in iOS 7 do not permit you to run on a scheduled basis.

Your best best actually is iOS7 even though you don't want to migrate to iOS7 - the background fetch being the relevant mode (even though you are pushing not fetching). With that background mode you will be able to have the opportunity to execute but not when you decide, only when the OS decides - and the frequency of that depends upon how the user uses your app. With iOS6 your options are even less restricted.

See iOS: Keep an app running like a service

Basically there just is no such thing as continuous background execution, nor periodic background execution, nor the app deciding when it wants to run when in the background.

If the user does not have an internet connection at the time they use your app to place the order then you should be notifying them of that anyway (if you don't then your app risks rejection from the app store) and maybe tell them to try again later.

If they are in flight mode the user will know they are in flight mode, if there is a temporary interruption (such as the phone is in an elevator or tunnel) then your app could keep on trying for as long as it is able - keep trying every minute while in the foreground, then when you switch to the background you know you have 10 minutes left, keep trying until the 10 minutes has nearly expired then post a local notification to the user notifying them that the app was unable to place the order due to lack of connectivity. If the user clicks on the notification and your app launches then the app will have the chance to retry again at that point.

If you still cannot make a connection then so be it, but you will have the chance to start the retry algorithm again. But at least you have notified the user their order has not gone through.

Community
  • 1
  • 1
Gruntcakes
  • 37,738
  • 44
  • 184
  • 378
  • Thanks for the downvote anonymous person. Care to explain why and justify? – Gruntcakes Feb 26 '14 at 17:54
  • Gotcha, thanks for the detailed answer :-). Ok then, I will go ahead and probably use it as I already have it. I have one question though... You said the background thread might run only 10 minutes max, but I made a NSLog() every time it fires and after more than 35 minutes I still had the Output in the console! May this have something to do with the fact, that I have started it from Xcode on an actual device? – gasparuff Feb 26 '14 at 18:03
  • I don't know, probably. I think its important with background functionality to run on an actual device. When you use beginBackgroundTaskWithExpirationHandler you can ask the OS how much time there is left to run, it might be interesting to see what the OS reports for that value on the simulator. – Gruntcakes Feb 26 '14 at 18:07
  • Hmm, I'm not using beginBackgroundTaskWithExpirationHandler, I'm just calling the `addOperationWithBlock`-thing. I guess I can't ask how much time there's left when I do it this way. – gasparuff Feb 26 '14 at 20:04
0

If what you need to know is if and when a data connection is available, I recommend inverting the process: rather then querying for a data connection, let your app be notified when a data connection is available. It's more efficient.

On this subject, I suggest using Reachability: you can make a call to know if a specific URL is accessible, and execute a block of code as soon as a connection is available.

Reachability *reach = [Reachability reacabilityWithHostName:@"www.myservice.com"];

...

reach.reachableBlock = ^(Reachability *reach) {
    // Process the requests queue
    // You should implement the method below
    [self processQueue];
}

...

if ([reach isReachable]) {
    // Upload the XML file to the server
    // You should implement the method below
    [self uploadToServer:myRequest];
} else {
    // Enqueue your request somewhere, for example into an NSArray
    // You should implement the method below
    [self addToQueue:myRequest];
}

The above code is meant to be a showcase (it doesn't work as is), use it as reference. I can just say that the reach variable should be a class property or data member, and that it should be initialized once. Also, if you enqueue your requests into an NSArray, be sure to do it in thread safe mode

Alternatively, Reachability can also notify via NSNotification when a connection is available - a different way to achieve the same result. Up to you to decide which one better fits with your needs.

Antonio
  • 71,651
  • 11
  • 148
  • 165
  • The OP is asking how about background execution. – Gruntcakes Feb 26 '14 at 16:12
  • Downvoted as answer doesn't address anything about long-term background execution and periodic checking which is the OPs's question. – Gruntcakes Feb 26 '14 at 16:17
  • I agree on that, but if I think there's a better and more elegant way to achieve the same result, shouldn't I suggest him? Maybe he didn't take this option into account. – Antonio Feb 26 '14 at 16:18
  • The question is specifically about long term background execution. Your answer is only relevant in very very short term background execution or foreground execution. As you're upset I'll revert the down vote just to make you happy. – Gruntcakes Feb 26 '14 at 16:22
  • No I'm not upset - really. Sorry if my comment made you think so. I respect opinions and critics, so feel free to downvote. I am aware that question and answer don't match - but in my opinion, at a higher level, problem and answer do match. I wrote my answer hoping it helps solving a problem. – Antonio Feb 26 '14 at 16:33
  • Thanks for the answer! Actually I was already thinking about using Reachability, but will this also work in background? – gasparuff Feb 26 '14 at 17:58
  • You can spawn a new thread or use grand central dispatch (GCD). Due to the async nature of data connection availability, I'd go for the GCD rather than manually creating new threads. In one of my projects I've created a dispatch queue, perform background tasks with dispatch async and used notifications in Reachability to build something that is similar to what you're implementing – Antonio Feb 26 '14 at 19:10
  • Ok, I will try these things out tomorrow and come back to give feedback. – gasparuff Feb 26 '14 at 20:07