[ios-必看] WWDC 2013 Session筆記 - iOS7中的多任務【轉】

感謝:http://onevcat.com/2013/08/ios7-background-multitask/javascript

  http://www.objc.io/issue-5/multitasking.htmlhtml

 

WWDC 2013 Session筆記 - iOS7中的多任務

iOS7的後臺多任務特性iOS7的後臺多任務特性java

這是個人WWDC2013系列筆記中的一篇,完整的筆記列表請參看這篇總覽。本文僅做爲我的記錄使用,也歡迎在許可協議範圍內轉載或使用,可是還煩請保留原文連接,謝謝您的理解合做。若是您以爲本站對您能有幫助,您可使用RSS郵件方式訂閱本站,這樣您將能在第一時間獲取本站信息。ios

本文涉及到的WWDC2013 Session有git

  • 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以前,系統所接受的應用多任務能夠大體分爲幾種:github

  • 後臺完成某些花費時間的特定任務
  • 後臺播放音樂等
  • 位置服務
  • IP電話(VoIP)
  • Newsstand

在WWDC 2013的keynote上,iOS7的後臺多任務改進被專門拿出來向開發者進行了介紹,到底iOS7裏多任務方面有什麼新的特性能夠利用,如何使用呢? 簡單來講,iOS7在後臺特性方面有很大改進,不只改變了以往的一些後臺任務處理方式,還加入了全新的後臺模式,本文將針對iOS7中新的後臺特性進行一 些學習和記錄。大致來講,iOS7後臺的變化在於如下四點:數據庫

  • 改變了後臺任務的運行方式
  • 增長了後臺獲取(Background Fetch)
  • 增長了推送喚醒(靜默推送,Silent Remote Notifications)
  • 增長了後臺傳輸(Background Transfer Service)

iOS7的多任務

後臺任務

首先看看後臺任務的變化,先說這方面的改變,而不是直接介紹新的API,是由於這個改變很典型地表明瞭iOS7在後臺任務管理和能耗控制上的大致思路。從上古時期開始(其實也就4.0),UIApplication提供了-beginBackgroundTaskWithExpirationHandler:方 法來使app在被切到後臺後仍然能保持運行一段時間,app能夠用這個方法來確保一些很重很慢的工做能夠在急不可耐的用戶將你的應用扔到後臺後還能完成, 好比編碼視頻,上傳下載某些重要文件或者是完成某些數據庫操做等,雖然時間不長,但在大多數狀況下勉強夠用。若是你以前沒有使用過這個API的話,它使用 起來大概是長這個樣子的:xcode

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和以前的系統中,系統在用戶退出應用後,若是應用正在執行後臺任務的話,系統會保持活躍狀態直到後臺任務完成或者是超時之後,纔會進入真正的低功耗休眠狀態。網絡

iOS6以前的後臺任務處理iOS6以前的後臺任務處理

而在iOS7中,後臺任務的處理方式發生了改變。系統將在用戶鎖屏後儘快讓設備進入休眠狀態,以節省電力,這時後臺任務是被暫停的。以後在設備在特 定時間進行系統應用的操做被喚醒(好比檢查郵件或者接到來電等)時,以前暫停的後臺任務將一塊兒進行。就是說,系統不會專門爲第三方的應用保持設備處於活動 狀態。以下圖示

iOS7的後臺任務處理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在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了。

更改Scheme的選項爲從後臺獲取事件中啓動更改Scheme的選項爲從後臺獲取事件中啓動

另外一種是當應用在後臺時,模擬一次後臺獲取。這個比較簡單,在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目錄下),不然若是因爲應用徹底退出的狀況致使沒有保存到可再次訪問的路徑的話,那可就白作工了。

後臺傳輸很是適合用於文件,照片或者追加遊戲內容關卡等的下載,若是配合後臺獲取或者靜默推送的話,相信能夠徹底不少頗有趣,而且之前被限制而沒法實現的功能。

相關文章
相關標籤/搜索