这是个人WWDC2013系列笔记中的一篇,完整的笔记列表请参看这篇总览。本文仅做为我的记录使用,也欢迎在许可协议范围内转载或使用,可是还烦请保留原文连接,谢谢您的理解合做。若是您以为本站对您能有帮助,您可使用RSS或邮件方式订阅本站,这样您将能在第一时间获取本站信息。git
本文涉及到的WWDC2013 Session有github
- Session 204 What’s New with Multitasking
- Session 705 What’s New in Foundation Networking
iOS7之前的Multitasking
iOS的多任务是在iOS4的时候被引入的,在此以前iOS的app都是按下Home键就被干掉了。iOS4虽然引入了后台和多任务,可是其实是伪多任务,通常的app后台并不能执行本身的代码,只有少数几类服务在经过注册后能够真正在后台运行,而且在提交到AppStore的时候也会被严格审核是否有越权行为,这种限制主要是出于对于设备的续航和安全两方面进行的考虑。以后通过iOS5和6的逐渐发展,后台能运行的服务的种类虽然出现了增长,可是iOS后台的本质并无变化。在iOS7以前,系统所接受的应用多任务能够大体分为几种:数据库
- 后台完成某些花费时间的特定任务
- 后台播放音乐等
- 位置服务
- IP电话(VoIP)
- Newsstand
在WWDC 2013的keynote上,iOS7的后台多任务改进被专门拿出来向开发者进行了介绍,到底iOS7里多任务方面有什么新的特性能够利用,如何使用呢?简单来讲,iOS7在后台特性方面有很大改进,不只改变了以往的一些后台任务处理方式,还加入了全新的后台模式,本文将针对iOS7中新的后台特性进行一些学习和记录。大致来讲,iOS7后台的变化在于如下四点:xcode
- 改变了后台任务的运行方式
- 增长了后台获取(Background Fetch)
- 增长了推送唤醒(静默推送,Silent Remote Notifications)
- 增长了后台传输(Background Transfer Service)
iOS7的多任务
后台任务
首先看看后台任务的变化,先说这方面的改变,而不是直接介绍新的API,是由于这个改变很典型地表明了iOS7在后台任务管理和能耗控制上的大致思路。从上古时期开始(其实也就4.0),UIApplication提供了-beginBackgroundTaskWithExpirationHandler:
方法来使app在被切到后台后仍然能保持运行一段时间,app能够用这个方法来确保一些很重很慢的工做能够在急不可耐的用户将你的应用扔到后台后还能完成,好比编码视频,上传下载某些重要文件或者是完成某些数据库操做等,虽然时间不长,但在大多数状况下勉强够用。若是你以前没有使用过这个API的话,它使用起来大概是长这个样子的:安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
- (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; } |
在beginBackgroundTaskWithExpirationHandler:
里写一个超时处理(系统只给app分配了必定时间来进行后台任务,超时以前会调用这个block),而后进行开始进行后台任务处理,在任务结束或者过时的时候call一下endBackgroundTask:
使之与begin方法配对(不然你的app在后台任务超时的时候会被杀掉)。同时,你可使用UIApplication实例的backgroundTimeRemaining属性来获取剩余的后台执行时间。网络
具体的执行时间来讲,在iOS6和以前的系统中,系统在用户退出应用后,若是应用正在执行后台任务的话,系统会保持活跃状态直到后台任务完成或者是超时之后,才会进入真正的低功耗休眠状态。session
而在iOS7中,后台任务的处理方式发生了改变。系统将在用户锁屏后尽快让设备进入休眠状态,以节省电力,这时后台任务是被暂停的。以后在设备在特定时间进行系统应用的操做被唤醒(好比检查邮件或者接到来电等)时,以前暂停的后台任务将一块儿进行。就是说,系统不会专门为第三方的应用保持设备处于活动状态。以下图示
这个变化在不减小应用的后台任务时间长度的状况下,给设备带来了更多的休眠时间,从而延长了续航。对于开发者来讲,这个改变动多的是系统层级的变化,对于非网络传输的任务来讲,保持原来的用法便可,新系统将会按照新的唤醒方式进行处理;而对于原来在后台作网络传输的应用来讲,苹果建议在iOS7中使用NSURLSession
,建立后台的session并进行网络传输,这样能够很容易地利用更好的后台传输API,而没必要受限于原来的时长,关于这个具体的咱们一下子再说。
后台获取(Background Fetch)
如今的应用没法在后台获取信息,好比社交类应用,用户必定须要在打开应用以后才能进行网络链接,获取新的消息条目,而后才能将新内容呈现给用户。说实话这个体验并非很好,用户在打开应用后一定会有一段时间的等待,每次皆是如此。iOS7中新加入的后台获取就是用来解决这个不足的:后台获取干的事情就是在用户打开应用以前就使app有机会执行代码来获取数据,刷新UI。这样在用户打开应用的时候,最新的内容将已然呈如今用户眼前,而省去了全部的加载过程。想一想看,没有加载的网络体验的世界,会是怎样一种感受。这已经不是smooth,而是真的amazing了。
那具体应该怎么作呢?一步一步来:
启用后台获取
首先是修改应用的Info.plist,在UIBackgroundModes
中加入fetch,便可告诉系统应用须要后台获取的权限。另一种更简单的方式,得益于Xcode5的Capabilities特性(参见能够参见我以前的一篇WWDC2013笔记 Xcode5和ObjC新特性),如今甚至都不须要去手动修改Info.plist来进行添加了,打开Capabilities页面下的Background Modes选项,并勾选Background fetch选项便可(以下图)。
在Capabilities中开启Background Modes
笔者写这篇文章的时候iOS7尚未上市,也没有相关的审核资料,因此不知道若是只是在这里打开了fetch选项,但却没有实现的话,应用会不会没法经过审核。可是依照苹果一向的作法来看,若是声明了须要某项后台权限,可是结果却没有相关实现的话,被拒掉的可能性仍是比较大的。所以你们尽可能不要拿上线产品进行实验,而应当是在demo项目里研究明白之后再到上线产品中进行实装。
设定获取间隔
对应用的UIApplication实例设置获取间隔,通常在应用启动的时候调用如下代码便可:
1
|
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; |
若是不对最小后台获取间隔进行设定的话,系统将使用默认值UIApplicationBackgroundFetchIntervalNever
,也就是永远不进行后台获取。固然,-setMinimumBackgroundFetchInterval:
方法接受的是NSTimeInterval,所以你也能够手动指定一个以秒为单位的最小获取间隔。须要注意的是,咱们都已经知道iOS是一个很是霸道为我独尊的系统,所以天然也不可能让一介区区第三方应用来控制系统行为。这里所指定的时间间隔只是表明了“在上一次获取或者关闭应用以后,在这一段时间内必定不会去作后台获取”,而真正具体到何时会进行后台获取,那彻底是要看系统娘的心情的咱们是无从得知的。系统将根据你的设定,选择好比接收邮件的时候顺便为你的应用获取一下,或者也有可能专门为你的应用唤醒一下设备。做为开发者,咱们应该作的是为用户的电池考虑,尽量地选择合适本身应用的后台获取间隔。设置为UIApplicationBackgroundFetchIntervalMinimum的话,系统会尽量多尽量快地为你的应用进行后台获取,可是好比对于一个天气应用,可能对实时的数据并不会那么关心,就彻底没必要设置为UIApplicationBackgroundFetchIntervalMinimum,也许1小时会是一个更好的选择。新的Mac OSX 10.9上已经出现了功耗监测,用于让用户肯定什么应用是能耗大户,有理由相信一样的东西也可能出如今iOS上。若是不想让用户由于你的应用是耗电大户而怒删的话,从如今开始注意一下应用的能耗仍是蛮有必要的(作绿色环保低碳的iOS app,从今天开始~)。
实现后台获取代码并通知系统
在完成了前两步后,只须要在AppDelegate里实现-application:performFetchWithCompletionHandler:
就好了。系统将会在执行fetch的时候调用这个方法,而后开发者须要作的是在这个方法里完成获取的工做,而后刷新UI,并通知系统获取结束,以便系统尽快回到休眠状态。获取数据这是应用相关的内容,在此不作赘述,应用在前台能完成的工做在这里都能作,惟一的限制是系统不会给你很长时间来作fetch,通常会小于一分钟,并且fetch在绝大多数状况下将和别的应用共用网络链接。这些时间对于fetch一些简单数据来讲是足够的了,好比微博的新条目(大图除外),接下来一小时的天气状况等。若是涉及到较大文件的传输的话,用后台获取的API就不合适了,而应该使用另外一个新的文件传输的API,咱们稍后再说。相似前面提到的后台任务完成时必须通知系统同样,在在获取完成后,也必须通知系统获取完成,方法是调用-application:performFetchWithCompletionHandler:
的handler。这个CompletionHandler接收一个UIBackgroundFetchResult
做为参数,可供选择的结果有UIBackgroundFetchResultNewData
,UIBackgroundFetchResultNoData
,UIBackgroundFetchResultFailed
三种,分别表示获取到了新数据(此时系统将对如今的UI状态截图并更新App Switcher中你的应用的截屏),没有新数据,以及获取失败。写一个简单的例子吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//File: YourAppDelegate.m -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { UINavigationController *navigationController = (UINavigationController*)self.window.rootViewController; id fetchViewController = navigationController.topViewController; if ([fetchViewController respondsToSelector:@selector(fetchDataResult:)]) { [fetchViewController fetchDataResult:^(NSError *error, NSArray *results){ if (!error) { if (results.count != 0) { //Update UI with results. //Tell system all done. completionHandler(UIBackgroundFetchResultNewData); } else { completionHandler(UIBackgroundFetchResultNoData); } } else { completionHandler(UIBackgroundFetchResultFailed); } }]; } else { completionHandler(UIBackgroundFetchResultFailed); } } |
固然,实际状况中会比这要复杂得多,用户当前的ViewController是否合适作获取,获取后的数据如何处理都须要考虑。另外要说明的是上面的代码在获取成功后直接在appDelegate里更新UI,这只是为了能在同一处进行说明,但倒是不正确的结构。比较好的作法是将获取和更新UI的业务逻辑都放到fetchViewController里,而后向其发送获取消息的时候将completionHandler做为参数传入,并在fetchViewController里完成获取结束的报告。
另外一个比较神奇的地方是系统将追踪用户的使用习惯,并根据对每一个应用的使用时刻给一个合理的fetch时间。好比系统将记录你在天天早上9点上班的电车上,中午12点半吃饭时,以及22点睡觉前会刷一下微博,只要这个习惯持续个三四天,系统便会将应用的后台获取时刻调节为9点,12点和22点前一点。这样在每次你打开应用都直接有最新内容的同时,也节省了电量和流量。
后台获取的调试
既然是系统决定的fetch,那咱们要如何测试写的代码呢?难道是将应用退到后台,而后安心等待系统进行后台获取么?固然不是…Xcode5为咱们提供了两种方法来测试后台获取的代码。一种是从后台获取中启动应用,另外一种是当应用在后台时模拟一次后台推送。
对于前者,咱们能够新建一个Scheme来专门调试从后台启动。点击Xcode5的Product->Scheme->Edit Scheme(或者直接使用快捷键⌘<
)。在编辑Scheme的窗口中点Duplicate Scheme按钮复制一个当前方案,而后在新Scheme的option中将Background Fetch打上勾。从这个Scheme来运行应用的时候,应用将不会直接启动切入前台,而是调用后台获取部分代码并更新UI,这样再点击图标进入应用时,你应该能够看到最新的数据和更新好的UI了。
另外一种是当应用在后台时,模拟一次后台获取。这个比较简单,在app调试运行时,点击Xcode5的Debug菜单中的Simulate Background Fetch,便可模拟完成一次获取调用。
推送唤醒(Remote Notifications)
远程推送(Remote Push Notifications)能够说是增长用户留存率的不二法则,在iOS6和以前,推送的类型是很单一的,无非就是显示标题内容,指定声音等。用户经过解锁进入你的应用后,appDelegate中经过推送打开应用的回调将被调用,而后你再获取数据,进行显示。这和没有后台获取时的打开应用后再获取数据刷新的问题是同样的。在iOS7中这个行为发生了一些改变,咱们有机会使设备在接收到远端推送后让系统唤醒设备和咱们的后台应用,并先执行一段代码来准备数据和UI,而后再提示用户有推送。这时用户若是解锁设备进入应用后将不会再有任何加载过程,新的内容将直接获得呈现。
实装的方法和刚才的后台获取比较相似,仍是一步步来:
启用推送唤醒
和上面的后台获取相似,更改Info.plist,在UIBackgroundModes
下加入remote-notification
便可开启,固然一样的更简单直接的办法是使用Capabilities。
更改推送的payload
在iOS7中,若是想要使用推送来唤醒应用运行代码的话,须要在payload中加入content-available
,并设置为1。
1 2 3 4 |
aps { content-available: 1 alert: {...} } |

实现推送唤醒代码并通知系统
最后在appDelegate中实现-application:didReceiveRemoteNotification:fetchCompletionHandle:
。这部份内容和上面的后台获取部分彻底同样,在此再也不重复。
一些限制和应用的例子
由于一旦推送成功,用户的设备将被唤醒,所以这类推送不可能不受到限制。Apple将限制此类推送的频率,当频率超过必定限制后,带有content-available标志的推送将会被阻塞,以保证用户设备不被频繁唤醒。按照Apple的说法,这个频率在一小时内个位数次的推送的话不会有太大问题。
Apple给出了几个典型的应用情景,好比一个电视节目类的应用,当用户标记某些剧目为喜好时,当这些剧有更新时,能够给用户发送静默的唤醒推送通知,客户端在接到通知后检查更新并开始后台下载(注意后台下载的部分绝对不该该在推送回调中作,而是应该使用新的后台传输服务,后面详细介绍)。下载完成后发送一个本地推送告知用户新的内容已经准备完毕。这样在用户注意到推送并打开应用的时候,全部必要的内容已经下载完毕,UI也将切换至用户喜好的剧目,用户只须要点击播放便可开始真正使用应用,这绝对是无比顺畅和优秀的体验。另外一种应用情景是文件同步类,好比用户标记了一些文件为须要随时同步,这样用户在其余设备或网页服务上更改了这些文件时,能够发送静默推送而后使用后台传输来保持这些文件随时是最新。
若是您是一路看下来的话,不难发现其实后台获取和静默推送在不少方面是很相似的,特别是实现和处理的方式,可是它们适用的情景是彻底不一样的。后台获取更多地使用在泛数据模式下,也即用户对特定数据并非很关心,数据应该被更新的时间也不是很肯定,典型的有社交类应用和天气类应用;而静默推送或者是推送唤醒更多地应该是用户感兴趣的内容发生更新时被使用,好比消息类应用和内容型服务等。根据不一样的应用情景,选择合适的后台策略(或者混合使用二者),以带给用户绝佳体验,这是Apple所指望iOS7开发者作到的。
后台传输(Background Transfer Service)
iOS6和以前,iOS应用在大块数据的下载这一块限制是比较多的:只有应用在前台时能保持下载(用户按Home键切到后台或者是等到设备自动休眠均可能停止下载),在后台只有很短的最多十分钟时间能够保持网络链接。若是想要完成一个较大数据的下载,用户将不得不打开你的app而且基本无所事事。不少这种时候,用户会想要是在下载的时候能切到别的应用刷刷微博或者玩玩游戏,而后再切回来的就已经下载完成了的话,该有多好。iOS7中,这能够实现了。iOS7引入了后台传输的相关方式,用来保证应用退出后数据下载或者上传能继续进行。这种传输是由iOS系统进行管理的,没有时间限制,也不要求应用运行在前台。
想要实现后台传输,就必须使用iOS7的新的网络链接的类,NSURLSession。这是iOS7中引入用以替代陈旧的NSURLConnection的类,著名的AFNetworking甚至不惜从底层开始彻底重写以适配iOS7和NSURLSession(参见这里),NSURLSession的重要性可见一斑。在这里我主要只介绍NSURLSession在后台传输中的一些使用,关于这个类的其余用法和对原有NSURLConnection的增强,只作稍微带过而不展开,有兴趣深刻挖掘和使用的童鞋能够参看Apple的文档(或者更简单的方式是使用AFNetworking来处理网络相关内容,而不是直接和CFNetwork框架打交道)。
步骤和例子
后台传输的的实现也十分简单,简单说分为三个步骤:建立后台传输用的NSURLSession对象;向这个对象中加入对应的传输的NSURLSessionTask,并开始传输;在实现appDelegate里实现-application:handleEventsForBackgroundURLSession:completionHandler:
方法,以刷新UI及通知系统传输结束。接下来结合代码来看一看实际的用法吧~
首先咱们须要一个用于后台下载的session:
1 2 3 4 5 6 7 8 9 10 11 |
- (NSURLSession *)backgroundSession { //Use dispatch_once_t to create only one background session. If you want more than one session, do with different identifier static NSURLSession *session = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.yourcompany.appId.BackgroundSession"]; session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; }); return session; } |
这里建立并配置了NSURLSession,将其指定为后台session并设定delegate。
接下来向其中加入对应的传输用的NSURLSessionTask,并启动下载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//@property (nonatomic) NSURLSession *session; //@property (nonatomic) NSURLSessionDownloadTask *downloadTask; - (NSURLSession *)backgroundSession { //... } - (void) beginDownload { NSURL *downloadURL = [NSURL URLWithString:DownloadURLString]; NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; self.session = [self backgroundSession]; self.downloadTask = [self.session downloadTaskWithRequest:request]; [self.downloadTask resume]; } |
最后一步是在appDelegate中实现-application:handleEventsForBackgroundURLSession:completionHandler:
1 2 3 4 5 6 7 8 9 10 11 |
//AppDelegate.m - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { //Check if all transfers are done, and update UI //Then tell system background transfer over, so it can take new snapshot to show in App Switcher completionHandler(); //You can also pop up a local notification to remind the user //... } |
NSURLSession和对应的NSURLSessionTask有如下重要的delegate方法可使用:
1 2 3 4 |
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location; - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error; |
一旦后台传输的状态发生变化(包括正常结束和失败)的时候,应用将被唤醒并运行appDelegate中的回调,接下来NSURLSessionTask的委托方法将在后台被调用。虽然上面的例子中直接在appDelegate中call了completionHandler,可是实际上更好的选择是在appDelegate中暂时持有completionHandler,而后在NSURLSessionTask的delegate方法中检查是否确实完成了传输并更新UI后,再调用completionHandler。另外,你的应用到如今为止只是在后台运行,想要提醒用户传输完成的话,也许你还须要在这个时候发送一个本地推送(记住在这个时候你的应用是能够执行代码的,虽然是在后台),这样用户能够注意到你的应用的变化并回到应用,并开始已经准备好数据和界面。
一些限制
首先,后台传输只会经过wifi来进行,用户大概也不会开心蜂窝数据的流量被后台流量用掉。后台下载的时间与之前的关闭应用后X分钟的模式不同,而是为了节省电力变为离散式的下载,并与其余后台任务并发(好比接收邮件等)。另外还须要注意的是,对于下载后的内容不要忘记写到应用的目录下(通常来讲这种能够重复得到的内容应该放到cache目录下),不然若是因为应用彻底退出的状况致使没有保存到可再次访问的路径的话,那可就白作工了。
后台传输很是适合用于文件,照片或者追加游戏内容关卡等的下载,若是配合后台获取或者静默推送的话,相信能够彻底不少颇有趣,而且之前被限制而没法实现的功能。