在ios7之前,当你的app进入后台之后,那么你能做的事情非常少。只有VOIP和定位服务等一些基本服务可以在后台运行,对于其他的的服务,只能通过后台任务来执行,而且仅限制在几分钟之内。如果你想下载一个很大的视频或者备份你的照片到服务器,那么你可能完不成这些任务,进程就被挂起了。
iOS7添加了两个新的接口,通过这两个接口你能够在后台更新的你UI和内容。第一,background fetch,ios7允许定期从网络获取新的内容。第二,远程通知,这是一个新的特性,利用了推送来通知app某些事件发生了。这两个新的特性能够让你在后台进行网络传输。
以上的两个特性都是通过app代理实现的,在程序被挂起之前你有30秒的时间来执行程序。
对于多任务来所,唯一明显的变化是新的app多任务的切换,当程序进入后台的时候会展示一个应用程序的快照。但是这个快照是可以更新的,在下面我们将看到如何更新这个快照。
Background Fetch
Background Fetch是一种智能的轮训机制,对于更新比较频烦的应用来说能表现出非常好的效果。系统会根据用户的行为来唤醒程序,并且在程序启动之前触发后台获取。例如,用户每天在1点中使用我们的app,系统会学习并且适应这个用户的习惯,所以,系统会每天在1点之前的一段时间内进行后台获取,为了减少电池的损耗,ios统一通过设备的无线电来进行获取。
第一步,如果你想进行后台获取,那么你需要在plist文件中指定UIBackgroundModes键,指定这个键的最简单的办法是利用xcode5的"Capabilities"选项卡,直接打开Background Modes进行勾选,或者你也可以手动编辑:
<key>UIBackgroundModes</key> <array> <string>fetch</string> </array>接下来告诉系统你想多久fetch一次:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; return YES; }默认的fetch间隔是永远不进行fetch,所以你需要告诉ios一个时间间隔,否则程序在后台永远不会被唤醒。UIApplicationBackgroundFetchIntervalMinimum告诉系统去管理这个程序的唤醒,越often越好。但是如果你觉得这样做没必要的话那么你需要自己指定一个时间间隔。比如一个天气的app每小时进行一次更新,那么系统至少要等一个小时进行fetch。
如果你的app允许用户logout,并且退出后又没什么新的数据,那么设置这个间隔为UIApplicationBackgroundFetchIntervalNever,这样可以节约资源。
最后一步是在你的delegate实现如下代码:
- (void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration]; NSURL *url = [[NSURL alloc] initWithString:@"http://yourserver.com/data.json"]; NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { completionHandler(UIBackgroundFetchResultFailed); return; } // Parse response/data and determine whether new content was available BOOL hasNewData = ... if (hasNewData) { completionHandler(UIBackgroundFetchResultNewData); } else { completionHandler(UIBackgroundFetchResultNoData); } }]; // Start the task [task resume]; }这个代理就是app被唤醒工作的地方,记住你只有30秒时间进行update,当update结束之后你需要调用处理结束程序。
完成处理程序有两个目的,一是衡量启动这个进程耗费的资源,并且根据传递的UIBackgroundFetchResult这个参数来判断新的数据是否available。二,当你调用完成处理程序的时候,程序的快照被更新了,在多任务的新APIs当中,所有完成处理程序的快照行为是相同的。
在程序的世界当中,你应改传递完成处理程序最为你的程序的子组建。当数据处理结束的时候调用这个完成处理程序,更新你的UI。
在这一点上,你可能好奇ios是如何在后台更新你的UI的,生命周期在后台获取时是什么样的。如果你的程序当前被挂起了,那么系统会唤醒你的程序在调用application:performFetchWithCompletionHandler:之前。如果你当前的程序没有运行,那么系统将会launch它,调用一些usual方法,像application: didFinishLaunchingWithOptions:,你完全可以把这个过程想象为用户从Springboard启动了程序,只不过你看不到UI和渲染的屏幕。
在大多数情况下,在程序启动的时候我们要做的事情都是相同的,不管是前台启动还是后台启动。但是我们仍然可以捕捉到是从什么状态下启动程序的,通过查看UIApplication的applicationState属性:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState); return YES; }
测试Background Fetch
有两种方法来测试Background fetch,最简单的方法是从xcode启动的你的app,然后点击debug菜单下的Simulate Background Fetch,另外一种方式是制定一个计划,让xcode按照计划来运行程序,在Product菜单下选择Scheme,然后 Manage Schemes,在这你可以编辑一个计或者添加一个计划。
Remote Notifications
推送的意义和功能这里不再赘述。当你接到推送的时候会调用
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"Remote Notification userInfo is %@", userInfo); NSNumber *contentID = userInfo[@"content-id"]; // Do something with the content ID completionHandler(UIBackgroundFetchResultNewData); }
同样,系统给你30秒时间来处理这些事务。
NSURLSession and Background Transfer Service
NSURLSession在ios7 当中是一个新的类,它是在基础网络中的新技术。为了替代NSURLConnection,一些相似的概念和类被封装了,像 NSURL
, NSURLRequest
,和NSURLResponse。所以你将用
NSURLSessionTask替代NSURLConnection来进行网络请求和处理响应。有三种类型的session tasks,data,download,upload, 这三种用法差不多,会了一个其他的就会了。
一个NSURLSession标示了一个或多个NSURLSessionTask,它会根据创建这些task的NSURLSessionConfiguration的不同而作出不同的行为。你可能会创建NSURLSession
s,根据NSURLSessionConfiguration进行分组。为了能够跟后台进行互动,你需要创建[NSURLSessionConfiguration backgroundSessionConfiguration],使用了这个sesion的task在外部的一个进程运行,即使你的程序崩溃了或者挂起了或者被杀死了,那么这个进程不受影响。
NSURLSessionConfiguration允许你设定HTTP头,允许你指定缓存策略,限制使用蜂窝网络等。一个需要注意的地方是discretionary标志,它标识系统是否以最佳性能调度任务,也就是说,你的设备需要在wifi下并且电源充足,否则后台传输不能够被执行。
NSURLSessionDownloadTask
- (NSURLSession *)backgroundURLSession { static NSURLSession *session = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *identifier = @"io.objc.backgroundTransferExample"; NSURLSessionConfiguration* sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier]; session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; }); return session; }
//接到推送的委托
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"Received remote notification with userInfo %@", userInfo); NSNumber *contentID = userInfo[@"content-id"]; NSString *downloadURLString = [NSString stringWithFormat:@"http://yourserver.com/downloads/%d.mp3", [contentID intValue]]; NSURL* downloadURL = [NSURL URLWithString:downloadURLString]; NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; NSURLSessionDownloadTask *task = [[self backgroundURLSession] downloadTaskWithRequest:request]; task.taskDescription = [NSString stringWithFormat:@"Podcast Episode %d", [contentID intValue]]; [task resume]; completionHandler(UIBackgroundFetchResultNewData); }
我们创建了一个下载任务,并对它的请求进行了配置,还提供了接下来要用到的描述等。你必须要要执行[task resume]来启动任务,这样任务才能在app挂起状态下启动执行。
现在我们要做的是实现NSURLSessionDownloadDelegate这个委托,用来接收任务完成的消息。在session的声明周期里如果你想要处理认证或者其他的事件你需要实现
NSURLSessionDelegate
委托或者 NSURLSessionTaskDelegate委托。你应该看看苹果的文档
https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html#//apple_ref/doc/uid/10000165i-CH2-SW42,它介绍了各种类型的session tasks的整个声明周期。
NSURLSessionDownloadDelegate
的所有方法都是需要实现的,虽然我们的例子只用了[NSURLSession downloadTask:didFinishDownloadingToURL:] 这个。当任务结束下载的时候,问及那被存储到了一个临时目录,你必须移动或copy这个文件到你的app,否则下次回调这个代理的时候这个文件会被删除。下面是这些代理:
#Pragma Mark - NSURLSessionDownloadDelegate - (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location); // Copy file to your app's storage with NSFileManager // ... // Notify your UI }
- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { }
- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { }
如果当session task运行完成了,而此时的app又是在前台活跃的,那么上面的代码就足够了。实际上,在大对数情况下你的app没在运行状态或者被挂起了,在这种情况下你必须实现两个application 委托方法,这样系统就能唤醒的你程序。不想之前的委托,application委托方法被回调两次,这时你的session或者task的委托可能会受到几条消息。application的application: handleEventsForBackgroundURLSession:函数在 NSURLSession
delegate回调之前被调用,URLSessionDidFinishEventsForBackgroundURLSession
在之后被调用,在前面的委托里你需要存储完成处理代码,在接下来的委托里通过完成处理程序更新你的UI。
- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { // You must re-establish a reference to the background session, // or NSURLSessionDownloadDelegate and NSURLSessionDelegate methods will not be called // as no delegate is attached to the session. See backgroundURLSession above. NSURLSession *backgroundSession = [self backgroundURLSession]; NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession); // Store the completion handler to update your UI after processing session events [self addCompletionHandler:completionHandler forSession:identifier]; } - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { NSLog(@"Background URL session %@ finished events.\n", session); if (session.configuration.identifier) { // Call the handler we stored in -application:handleEventsForBackgroundURLSession: [self callCompletionHandlerForSession:session.configuration.identifier]; } } - (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier { if ([self.completionHandlerDictionary objectForKey:identifier]) { NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n"); } [self.completionHandlerDictionary setObject:handler forKey:identifier]; } - (void)callCompletionHandlerForSession: (NSString *)identifier { CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier]; if (handler) { [self.completionHandlerDictionary removeObjectForKey: identifier]; NSLog(@"Calling completion handler for session %@", identifier); handler(); } }
如果你的程序没有在前台运行,那么这两阶段的处理,在background transfer结束的时候,对于更新你的UI来说是必要的。如果在background transfer结束的时候,你的程序根本没启动,那么系统会启动你的程序,以前的application和session的delegate会在application:didFinishLaunchingWithOptions:之后执行。
Configuration and Limitation
我们已经简要的介绍后台传输服务的耗电情况,但是你应该通读上面提供的文档,看看NSURLSessionConfiguration
的options当中哪条最适合你。除了download tasks ,NSURLSession
也全面支持upload tasks。
background sessions也有一些限制,在NSURLSession你不能使用block-based的回调,在后台启动程序是一件很费资源的事,所以http的重定向经常被用到。background sessions只支持http和https。系统会根据可用的资源对background sessions进行优化。所以你的background session不可能总在后台服务。
NSURLSessionDataTasks
只能短期的存活,进行小的请求,不能用来下载和上传。
相关推荐
清除iOS 7 多任务调整。 通过长按应用程序快照和应用程序图标(如 1.2betas)杀死应用程序切换器中的所有应用程序。 新增功能—— v1.2 如果正在播放音乐,则在杀死所有应用程序时排除正在播放的应用程序。 长按应用...
仿iOS9多任务切换的卡片流。.zip,MXCardLayout reproduces the behavior seen in iOS9's multitasking switcher.
多线程多任务下载实例代码 传个资源这个费事 写那么多说明
手动开启ios下的多任务手势 越狱什么的都是可以的
NULL 博文链接:https://o0o0o0o.iteye.com/blog/1312452
iOS 7学习:多任务处理之Background Fetch对应的Demo,主要探讨Background Fetch后台工作模式。
iPhone多任务执行源码 Demo 实现iOS的多任务执行功能
iOSGCD控制多线程任务数量,直接使用ZKRGlobalQueue^()。
iOS 7 SDK: 如何使用后台获取功能源码,该案例主要教你如何使用iOS 7 SDK多任务处理API--Background Fetch。我们生活在一个社交化的世界中,大部分用户都安装了几个社交类app,但是每次用户打开app,他们必须要等待...
支持多任务下载,经测试基本可实现多任务下载功能
evad3rs越狱“梦之队”意外放出了iOS 7.x完美越狱工具,该工具支持全系iOS设备,包括所有的iPhone、iPod touch、iPad、和iPad mini,系统支持iOS 7.0-iOS 7.0.4任何版本。本版已经彻底移除太极助手。 相比上次...
作者zqpmaster,源码MXCardLayout,使用UICollectionViewLayout仿IOS9的多任务切换做的动画。 介绍两种方式进行安装 CocoaPods 手动将 MXCardLayout 拖入项目中
基于AFNetworking2.0实现多任务断点下载。
iOS常见的多线程:GCD 任务和队列,异步执行 + 并发队列,线程锁,信号量
开辟多线程任务,说到底,我就是好久没来装逼了
这个教程将会教会你如何在后台传输数据,如何使用iOS7提供的多任务API。我将会教会你如何在后台下载一个文件,并且在文件下载完成时弹出一个本地的提醒。 后台数据传输起源于iOS6,允许在前台或者后台下载数据,但是...
ios demo,dispatch_async,DISPATCH_QUEUE_CONCURRENT,多任务并发执行,自动创建多线
2. ... 3. }] 1. { 4. }, 6. } 2. didReceiveRemoteNotification:(NSDictionary *)use
多任务 12 数据保护 13 苹果推送通知服务 13 本地通知 14 手势识别器 14 文件共享支持 14 点对点服务 15 标准系统视图控制器 15 外部设备支持 16 Cocoa Touch 层包含的框架 16 Address Book UI 框架 16 Event Kit UI...