Efficiently sync data in iOS

最后都变了- 提交于 2019-12-13 04:48:21

问题


I am working on app where I sync data to and from the server, when app enters background and app becomes active. Because as I sync to multiple devices, the data should be updated on all devices.

So here is how I do it,

- (void)applicationDidBecomeActive:(UIApplication *)application
{
     if([[AppManager instance].globalManager activeSyncCounter] == 0)
     {
           [_webService callServerAPI];         
     }
}


- (void)applicationDidEnterBackground:(UIApplication *)application
{
    if([[AppManager instance].globalManager activeSyncCounter] == 0)
     {
           [_webService callServerAPI];         
     }
}

Let me explain the code above

1) I am calling APIs to sync my data, I iterate through the for loop to sync my data and send it to server.( all calls are async)

2) As you can see I have used activeSyncCounter. I have used this because, user can open and exit the app immediately, so that server APIs are not called again, till first one finishes.

Now here are my doubts.

1) Firstly when I receive response from server, when the data is more, it takes times to update going through the for loop, and my UI becomes unresponsive. So for this to improve, I need to run the for loop code inside a dispatch queue. Or is their any other better way to do it?

Also, as updating is done, when app enters background, so do I need dispatch queue when app enters background?

2) Secondly, is their any alternative for using activeSyncCounter


回答1:


You (almost) never want to do any synchronous I/O operations (including network operations) on the main thread, so to you first question, yes, you should either use asynchronous networking APIs or move the synchronous operation to a different thread -- dispatch queues are one way to do that.

As to your second question, I don't know that there's definitively a "better" way, but there are different ways. One thing that I might suggest would be to move that check into callServerAPI so as to simplify the code that calls that method. (i.e. write the checking code once, instead of everywhere you call the server API.)




回答2:


It's important build your network operations to run asynch and finish by calling a completion block. NSURLConnection provides a convenience method called sendAsynchronousRequest:completionHandler:, which does just that.

Since some of the work you want done must be done upon entering the background, your app needs to be kept alive long enough to finish. Apple provides a complete article here, but the gist is (tweaking their sample code for your situation) ...

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // this is your call, modified to use a completion block
    [_webService callServerAPIWithCompletion:^(id result, NSError *error) {
        // set state to indicate that the synch finished...see below
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;        
    }];  
}

So I think you have two bits of work to do: (1) refactor your network code to run asynch and finish by calling a block, and (2) come up with a scheme to throttle how often you run the network request.

On the latter point, consider saving an NSDate to NSUserDefaults. Then you can ask:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDate *lastSynch = [defaults objectForKey:@"MyApp_lastSynch"];

NSTimeInterval sinceLastSynch = -[lastSynch timeIntervalSinceNow];
if (sinceLastCheck < 3600) {
    // we did the last synch less than an hour ago.  skip it for now
} else {
   // do it
}

In the completion block of the synch, write down the current time that you finished:

 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSDate date] forKey:@"MyApp_lastSynch"];
[defaults synchronize];

EDIT - here's how your network code might look if it used a block:

- (void)pushDataWithCompletion:(void (^)(BOOL, NSError))completion;

Putting all this together, your app delegate code might look best like this:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // don't bother if we did this recently (60 sec in this e.g.
    if ([self timeSinceLastPushData] < 60) return;

    bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];

    // this is your call, modified to use a completion block
    [pushDataWithCompletion:^(BOOL success, NSError *error) {
        if (success) {
             NSLog(@"success.  right down the time");
             [self pushDataComplete];
        } else {
            NSLog(@"error %@, but we'll still tell os that we're done", error);
        }
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;        
    }];  
}

- (void)pushDataComplete {
     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:[NSDate date] forKey:@"MyApp_lastSynch"];
    [defaults synchronize];
}

- (NSTimeInterval)timeSinceLastPushData {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDate *lastSynch = [defaults objectForKey:@"MyApp_lastSynch"];

    return -[lastSynch timeIntervalSinceNow];
}


来源:https://stackoverflow.com/questions/28216547/efficiently-sync-data-in-ios

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!