2016-01-22 76 views
4

所以这个网页上有关于后台执行的例子:https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW1,这里是例子:苹果编程指南iOS背景执行指南的例子?

- (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; 
    }]; 

    // 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. 

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

} 

据说bgTask在类作为变量定义。因此,每个类(对象)的实例都有一个bgTask属性。如果applicationDidEnterBackground在异步块完成之前被多次调用,是否存在竞态条件的危险?我的意思是bgTask会改变它的值,而endBackgroundTask将被调用新的任务值,而不是旧值?

这里不是更好的解决方案来做到这一点:

__block UIBackgroundTaskIdentifier bgTask; 

调用beginBackgroundTaskWithName过吗?

+0

,你应该检查是否bgTask是不是无效,并分配给它新的价值之前结束它。 –

+0

if(_bgTask!= UIBackgroundTaskInvalid) [[UIApplication sharedApplication] endBackgroundTask:_bgTask]; WEAK_SELF weakSelf = self; _bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^ {[UIApplication sharedApplication] endBackgroundTask:weakSelf.bgTask]; weakSelf.bgTask = UIBackgroundTaskInvalid; }]; –

回答

2

对于每个对象,有一个bgTask的实例,但这是AppDelegate,而不是一些常规的VC或对象。所以在技术上将只有一个bgTask实例在工作。

但是,这仍然会产生问题。因为如果此方法被调用两次,它将覆盖bgTask的值。我的第一个想法是,在退出应用程序时,不止一次,所有以前的任务都会过期。但经过测试意识到情况并非如此(这是IMO的一件好事)。发生的是bgTask被覆盖(如预期)并且新值被传递给第一个endBackgroundTask:调用。之后立即bgTask设置为UIBackgroundTaskInvalid,将其清除并将清除的值传递给任何后续调用endBackgroundTask:。这显然导致了不清洁的终止,因为不是所有的唯一ID都会被终止,导致expiration处理器在任何左侧的后台任务上执行。

话虽这么说,我相信你对使用局部变量的假设是正确的。如果你尝试这个代码(放在AppDelegateapplicationDidEnterBackground:):

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

NSLog(@"Backgrounded: %@", @(bgTask)); 

// 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. 
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 
     NSLog(@"Done! %@", @(bgTask)); 
     [application endBackgroundTask:bgTask]; 
     bgTask = UIBackgroundTaskInvalid; 
    }); 
}); 

你会看到每个本地bgTask被分配一个独特的价值和正确完成10秒后(按照dispatch_after调用)。

1

我认为你遇到的问题如下:
应用程序发送到Background状态,iOS调用applicationDidEnterBackground:
后台任务已启动,可能需要几分钟的时间。
在此期间,应用程序被再次激活,并再次发送到后台,这就要求再次applicationDidEnterBackground:,并启动其他后台任务,从而可变bgTask将被重写,如果它不是一个块变量。这是对的。因此,bgTask确实应该是一个块变量。
与此相关的问题是问题,你如何能完成后台执行,如果多个后台任务已启动。一个例子,你如何做到这一点,给出here
这个想法是有一个变量来计算活动的后台任务。只要所有这些完成,就可以终止后台执行。

1

你是对的,第二次调用时,applicationDidEnterBackground会导致问题。但为了再次调用该方法,应用程序首先需要再次放到前台。所以解决方案很简单。只需拨打您的到期处理从applicationWillEnterForeground

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

- (void)applicationDidEnterBackground:(UIApplication *)application { 

    bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{ 
     [self expireBackgroundTask]; 
    }]; 

    // 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. 

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

} 

- (void)applicationWillEnterForeground:(UIApplication *)application 
{ 
    [self expireBackgroundTask]; 
}