后台任务块功能未完成

时间:2012-01-10 18:48:06

标签: iphone ios5 objective-c-blocks background-process

我正在开发一款iphone应用程序,偶尔会在后台触发一项任务,重新安排一些数据并将其上传到服务器。我已经使用了Grand Central Dispatch (GCD) with CoreData中的许多原则来运行,因为我正在编辑Core Data中持久存在的对象,但代码只是偶尔完成运行,尽管应用程序说它几乎完整的600秒剩余的执行时间。

我正在使用的代码:

__block UIBackgroundTaskIdentifier bgTask;
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance

NSLog(@"BackgroundTimeRemaining before block: %f", application.backgroundTimeRemaining);

bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    // Clean up any unfinished task business by marking where you.
    // stopped or ending the task outright.
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task, preferably in chunks.

    NSLog(@"BackgroundTimeRemaining after block: %f", application.backgroundTimeRemaining);
    NSLog(@"Fixing item in the background");

    //Create secondary managed object context for new thread
    NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
    [backgroundContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    /* Save the background context and handle the save notification */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(backgroundContextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:backgroundContext];

    //creating runloop to kill location manager when done
    NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:60];
    [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
    NSLog(@"Stop time = %@", stopDate);

    MasterViewController *masterViewContoller = [[MasterViewController alloc] init];
    masterViewContoller.managedObjectContext = backgroundContext;
    [[masterViewContoller locationManager] startUpdatingLocation];
    NSLog(@"Successfully fired up masterViewController class");

    [masterViewContoller adjustDataInBackground:FALSE];
    NSLog(@"Fixed Object!");

    //save background context
    [backgroundContext save:NULL];

    //unregister self for notifications
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:backgroundContext];

    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
});

问题是“adjustDataInBackground:FALSE”是一个非常长的方法,它调用其他支持方法(包括创建和保存核心数据对象),并且当后台任务不允许所有这些方法完成时它会破坏我的数据。

有没有更好的方法来处理这种操作?我是否需要将所有原始代码直接放入后台任务块?

1 个答案:

答案 0 :(得分:3)

事实证明,我有两个奇怪的事情正在绊倒后台任务:

  • 异步URL连接(当他们的启动方法完成时,iOS认为后台任务已完成,即使尚未收到响应)
  • 一个特定于后台任务的位置管理器(显然是一个主要的禁忌......苹果有一些关于此的文档,但控制台有时会发出错误)

这是我现在使用的代码(它到目前为止有效):

    __block UIBackgroundTaskIdentifier bgTask;
UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance

NSLog(@"BackgroundTimeRemaining before block: %f", application.backgroundTimeRemaining);

bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    // Clean up any unfinished task business by marking where you.
    // stopped or ending the task outright.
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // Do the work associated with the task, preferably in chunks.

    NSLog(@"BackgroundTimeRemaining after block: %f", application.backgroundTimeRemaining);

    //Create secondary managed object context for new thread
    NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
    [backgroundContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    /* Save the background context and handle the save notification */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(backgroundContextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:backgroundContext];

    //Set a grace period during which background updates can't stack up...
    //This number should be more than the longest combo of timeout values in adjustDataInBackground
    NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:90];
    __lastBackgroundSnapshot = stopDate;

    NSLog(@"Stop time = %@", stopDate);

    MasterViewController *masterViewContoller = [[MasterViewController alloc] init];
    masterViewContoller.managedObjectContext = backgroundContext;
    NSLog(@"Successfully fired up masterViewController class");

    [masterViewContoller adjustDataInBackground];
    NSLog(@"adjustDataInBackground!");

    //just in case
    [[self locationManager] stopUpdatingLocation];

    //save background context
    [backgroundContext save:NULL];

    NSLog(@"Uploading in background");
    //send results to server
    postToServer *uploadService = [[postToServer alloc] init];
    uploadService.managedObjectContext = backgroundContext;
    [uploadService uploadToServer];

    //save background context after objects are marked as uploaded
    [backgroundContext save:NULL];

    //unregister self for notifications
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:backgroundContext];

    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
});

此外,我将以下runloop添加到我的异步URLConnection对象中,以便它们保持足够长的时间来完成它们的业务。虽然它不是处理它的最优雅的方式,但是如果runloop在没有服务器交换完成的情况下结束,你可以优雅地处理故障。

一个runloop(根据任务调整不同的超时时间):

//marks the attempt as beginning
self.doneUpload = [NSNumber numberWithBool:FALSE];

[[uploadAttempt alloc] fireTheUploadMethod];

//if uploading in the background, initiate a runloop to keep this object alive until it times out or finishes
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground)
{
    //Timeout length to wait in seconds to allow for async background execution
    NSDate *stopDate = [[NSDate date] dateByAddingTimeInterval:120];

    do {
        NSLog(@"Waiting for upload to return, time left before timeout: %f", [stopDate timeIntervalSinceNow]);
        [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
    } while ([stopDate timeIntervalSinceNow] > 0 && self.doneUpload == [NSNumber numberWithBool:FALSE]);
}

希望这有助于将来遇到这种情况的任何人!