iphone - 后台任务 block 功能未完成

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

我正在开发一个 iPhone 应用程序,它偶尔会在后台触发一个任务来重新排列一些数据并将其上传到服务器。我使用了 Grand Central Dispatch (GCD) with CoreData 中的很多原则。让事情运行起来,因为我正在编辑核心数据中持久存在的对象,但代码只是偶尔完成运行,尽管应用程序说它还剩下几乎完整的 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”是一个相当长的方法,它调用其他支持方法(包括创建和保存核心数据对象),并且当后台任务不允许所有这些方法完成时,它会损坏我的数据。

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

最佳答案

事实证明,我发生了两件奇怪的事情,导致后台任务失败:

  • 异步 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;
});

此外,我将以下运行循环添加到我的异步 URLConnection 对象中,以便它们保持事件状态足够长的时间来完成其业务。虽然这不是最优雅的处理方式,但只要您能够在运行循环在服务器交换未完成的情况下结束时优雅地处理故障,它就可以工作。

运行循环(根据任务调整不同的超时):

//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]);
}

希望这对将来遇到此问题的人有所帮助!

关于iphone - 后台任务 block 功能未完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8808758/

相关文章:

ios - Google+ iOS SDK - 获取关注 "me"的人列表

iphone - 删除 uitextfield 开头的空白

ios - 如何在 UITableView 中的图像中添加事件指示器?

ios - 需要通过嵌套异步调用获得 "For Loop"的结果( Objective-C )

iphone - NSURLRequest POST 到谷歌应用引擎?

iphone - 高效地访问数组内的数组

iphone - Facebook API BUG?

iphone - 即使在实现 shouldAutorotateToInterfaceOrientation 后 View 也不会调整大小

iphone - iPhone 中的 SQLite 表更新

ios - 在 iOS 上使用 Grand Central Dispatch,如果常规 Objective-C block 不在调度队列中,它们会在哪个队列(如果有)上运行?