Eya*_*yal 99 network-programming objective-c multitasking ios background-thread
我对如何以及何时使用感到困惑beginBackgroundTaskWithExpirationHandler.
Apple在他们的示例中显示在applicationDidEnterBackground委托中使用它,以便有更多时间来完成一些重要任务,通常是网络事务.
在查看我的应用程序时,似乎我的大多数网络内容都很重要,当一个人启动时,如果用户按下主页按钮,我想完成它.
因此,beginBackgroundTaskWithExpirationHandler为了安全起见,包装每个网络事务(并且我不是在谈论下载大量数据,大多数是短xml)是接受/良好做法 吗?
Ash*_*lls 156
如果您希望网络事务在后台继续,那么您需要将其包装在后台任务中.endBackgroundTask你完成后打电话也很重要- 否则应用程序将在分配的时间到期后被杀死.
我看起来像这样:
- (void) doUpdate
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self beginBackgroundUpdateTask];
NSURLResponse * response = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
// Do something with the result
[self endBackgroundUpdateTask];
});
}
- (void) beginBackgroundUpdateTask
{
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}
- (void) endBackgroundUpdateTask
{
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
Run Code Online (Sandbox Code Playgroud)
我UIBackgroundTaskIdentifier为每个后台任务都有一个属性
Swift中的等效代码
func doUpdate () {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let taskID = beginBackgroundUpdateTask()
var response: URLResponse?, error: NSError?, request: NSURLRequest?
let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
// Do something with the result
endBackgroundUpdateTask(taskID)
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.shared.beginBackgroundTask(expirationHandler: ({}))
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.shared.endBackgroundTask(taskID)
}
Run Code Online (Sandbox Code Playgroud)
Joe*_*oel 20
接受的答案非常有用,在大多数情况下都应该没问题,但是有两件事让我感到困扰:
正如许多人所注意到的那样,将任务标识符存储为属性意味着如果多次调用该方法可以覆盖它,从而导致任务永远不会正常结束,直到操作系统在到期时强制结束.
beginBackgroundTaskWithExpirationHandler如果你有一个包含大量网络方法的更大的应用程序,这种模式需要每个调用的唯一属性,这似乎很麻烦.
为了解决这些问题,我编写了一个单例来处理所有管道并跟踪字典中的活动任务.无需跟踪任务标识符所需的属性.似乎运作良好.用法简化为:
//start the task
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTask];
//do stuff
//end the task
[[BackgroundTaskManager sharedTasks] endTaskWithKey:taskKey];
Run Code Online (Sandbox Code Playgroud)
或者,如果您想提供一个完成块,它可以执行超出任务(内置任务)的操作,您可以调用:
NSUInteger taskKey = [[BackgroundTaskManager sharedTasks] beginTaskWithCompletionHandler:^{
//do stuff
}];
Run Code Online (Sandbox Code Playgroud)
下面提供了相关的源代码(为简洁起见,排除了单例内容).评论/反馈欢迎.
- (id)init
{
self = [super init];
if (self) {
[self setTaskKeyCounter:0];
[self setDictTaskIdentifiers:[NSMutableDictionary dictionary]];
[self setDictTaskCompletionBlocks:[NSMutableDictionary dictionary]];
}
return self;
}
- (NSUInteger)beginTask
{
return [self beginTaskWithCompletionHandler:nil];
}
- (NSUInteger)beginTaskWithCompletionHandler:(CompletionBlock)_completion;
{
//read the counter and increment it
NSUInteger taskKey;
@synchronized(self) {
taskKey = self.taskKeyCounter;
self.taskKeyCounter++;
}
//tell the OS to start a task that should continue in the background if needed
NSUInteger taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endTaskWithKey:taskKey];
}];
//add this task identifier to the active task dictionary
[self.dictTaskIdentifiers setObject:[NSNumber numberWithUnsignedLong:taskId] forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//store the completion block (if any)
if (_completion) [self.dictTaskCompletionBlocks setObject:_completion forKey:[NSNumber numberWithUnsignedLong:taskKey]];
//return the dictionary key
return taskKey;
}
- (void)endTaskWithKey:(NSUInteger)_key
{
@synchronized(self.dictTaskCompletionBlocks) {
//see if this task has a completion block
CompletionBlock completion = [self.dictTaskCompletionBlocks objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (completion) {
//run the completion block and remove it from the completion block dictionary
completion();
[self.dictTaskCompletionBlocks removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
@synchronized(self.dictTaskIdentifiers) {
//see if this task has been ended yet
NSNumber *taskId = [self.dictTaskIdentifiers objectForKey:[NSNumber numberWithUnsignedLong:_key]];
if (taskId) {
//end the task and remove it from the active task dictionary
[[UIApplication sharedApplication] endBackgroundTask:[taskId unsignedLongValue]];
[self.dictTaskIdentifiers removeObjectForKey:[NSNumber numberWithUnsignedLong:_key]];
}
}
}
Run Code Online (Sandbox Code Playgroud)
pha*_*ann 19
这是一个封装运行后台任务的Swift类:
class BackgroundTask {
private let application: UIApplication
private var identifier = UIBackgroundTaskInvalid
init(application: UIApplication) {
self.application = application
}
class func run(application: UIApplication, handler: (BackgroundTask) -> ()) {
// NOTE: The handler must call end() when it is done
let backgroundTask = BackgroundTask(application: application)
backgroundTask.begin()
handler(backgroundTask)
}
func begin() {
self.identifier = application.beginBackgroundTaskWithExpirationHandler {
self.end()
}
}
func end() {
if (identifier != UIBackgroundTaskInvalid) {
application.endBackgroundTask(identifier)
}
identifier = UIBackgroundTaskInvalid
}
}
Run Code Online (Sandbox Code Playgroud)
最简单的使用方法:
BackgroundTask.run(application) { backgroundTask in
// Do something
backgroundTask.end()
}
Run Code Online (Sandbox Code Playgroud)
如果您需要在结束前等待委托回调,请使用以下内容:
class MyClass {
backgroundTask: BackgroundTask?
func doSomething() {
backgroundTask = BackgroundTask(application)
backgroundTask!.begin()
// Do something that waits for callback
}
func callback() {
backgroundTask?.end()
backgroundTask = nil
}
}
Run Code Online (Sandbox Code Playgroud)
正如此处以及其他 SO 问题的答案中所指出的,您不想beginBackgroundTask仅在您的应用程序进入后台时才使用;相反,您应该为任何耗时的操作使用后台任务,即使应用程序确实进入后台,您也希望确保其完成。
因此,您的代码最终可能会重复相同的样板代码以进行调用beginBackgroundTask和endBackgroundTask连贯。为了防止这种重复,想要将样板打包成某个单独的封装实体当然是合理的。
我喜欢这样做的一些现有答案,但我认为最好的方法是使用 Operation 子类:
您可以将 Operation 排入任何 OperationQueue 并按照您认为合适的方式操作该队列。例如,您可以提前取消队列上的任何现有操作。
如果你有不止一件事情要做,你可以链接多个后台任务操作。操作支持依赖。
操作队列可以(也应该)是后台队列;因此,无需担心在您的任务中执行异步代码,因为 Operation是异步代码。(实际上,在 Operation 中执行另一层异步代码是没有意义的,因为 Operation 甚至会在该代码开始之前完成。如果需要这样做,您将使用另一个 Operation。)
这是一个可能的操作子类:
class BackgroundTaskOperation: Operation {
var whatToDo : (() -> ())?
var cleanup : (() -> ())?
override func main() {
guard !self.isCancelled else { return }
guard let whatToDo = self.whatToDo else { return }
var bti : UIBackgroundTaskIdentifier = .invalid
bti = UIApplication.shared.beginBackgroundTask {
self.cleanup?()
self.cancel()
UIApplication.shared.endBackgroundTask(bti) // cancellation
}
guard bti != .invalid else { return }
whatToDo()
guard !self.isCancelled else { return }
UIApplication.shared.endBackgroundTask(bti) // completion
}
}
Run Code Online (Sandbox Code Playgroud)
如何使用它应该是显而易见的,但如果不是,请想象我们有一个全局 OperationQueue:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Run Code Online (Sandbox Code Playgroud)
因此,对于典型的耗时的一批代码,我们会说:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Run Code Online (Sandbox Code Playgroud)
如果您的一批耗时的代码可以分为多个阶段,那么如果您的任务被取消,您可能希望尽早退出。在这种情况下,只需从关闭中过早返回即可。请注意,您在闭包中对任务的引用需要弱,否则您将获得一个保留周期。这是一个人工插图:
let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
guard let task = task else {return}
for i in 1...10000 {
guard !task.isCancelled else {return}
for j in 1...150000 {
let k = i*j
}
}
}
backgroundTaskQueue.addOperation(task)
Run Code Online (Sandbox Code Playgroud)
如果您有清理工作要做,以防后台任务本身过早取消,我提供了一个可选的cleanup处理程序属性(未在前面的示例中使用)。其他一些答案因不包括这一点而受到批评。
| 归档时间: |
|
| 查看次数: |
47450 次 |
| 最近记录: |