在 iOS 7 之前,當(dāng)程序置于后臺(tái)之后開發(fā)者們對(duì)他們程序所能做的事情非常有限。除了 VOIP 和基于地理位置特性以外,唯一能做的地方就是使用后臺(tái)任務(wù)(background tasks)讓代碼可以執(zhí)行幾分鐘。如果你想下載比較大的視頻文件以便離線瀏覽,亦或者備份用戶的照片到你的服務(wù)器上,你都僅能完成一部分工作。
iOS 7 添加了兩個(gè)新的 API 以便你的程序可以在后臺(tái)更新界面以及內(nèi)容。首先是后臺(tái)獲取(Background Fetch),它允許你定期地從網(wǎng)絡(luò)獲取新的內(nèi)容。第二個(gè) API 就是遠(yuǎn)程通知(Remote Notifications),這是一個(gè)當(dāng)事件發(fā)生時(shí)可以讓推送通知主動(dòng)提醒應(yīng)用的新特性,這兩者都為你的應(yīng)用界面保持最新提供了極大的幫助。在新的后臺(tái)傳輸服務(wù) (Background Transfer Service) 中執(zhí)行定期的任務(wù),也允許你在進(jìn)程之外可以執(zhí)行網(wǎng)絡(luò)傳輸(下載和上傳)工作。
后臺(tái)獲取 (Background Fetch) 和遠(yuǎn)程通知 (Remote Notification) 基于簡(jiǎn)單的 ApplicationDelegate 鉤子,在應(yīng)用程序掛起之前的 30 秒時(shí)鐘時(shí)間執(zhí)行工作。它們不是用于 CPU 頻繁工作或者長(zhǎng)時(shí)間運(yùn)行任務(wù),而是用來(lái)處理長(zhǎng)時(shí)間運(yùn)行的網(wǎng)絡(luò)請(qǐng)求隊(duì)列,例如下載一部很大的電影,或者執(zhí)行快速的內(nèi)容更新。
對(duì)用戶來(lái)說(shuō),多任務(wù)處理有一點(diǎn)顯而易見的改變就是新的應(yīng)用切換程序 (the new app switcher),它用來(lái)呈現(xiàn)應(yīng)用到后臺(tái)時(shí)的界面快照。這些快照的存在是有一定理由的--現(xiàn)在你可以在后臺(tái)完成工作后更新程序快照,以用來(lái)呈現(xiàn)新的內(nèi)容。社交網(wǎng)絡(luò)、新聞或者天氣等應(yīng)用現(xiàn)在都可以直接呈現(xiàn)最新的內(nèi)容而不需要用戶重新打開應(yīng)用。我們稍后會(huì)介紹如何更新屏幕快照。
后臺(tái)獲取是一種智能的輪詢機(jī)制,它很適合需要經(jīng)常更新內(nèi)容的程序,像社交網(wǎng)絡(luò),新聞或天氣的程序。為了在用戶啟動(dòng)程序前提前觸發(fā)后臺(tái)獲取,系統(tǒng)會(huì)根據(jù)用戶行為喚醒應(yīng)用程序。舉個(gè)例子,如果用戶經(jīng)常在下午 1 點(diǎn)使用某個(gè)應(yīng)用程序,系統(tǒng)會(huì)學(xué)習(xí),適應(yīng)并在使用周期前執(zhí)行后臺(tái)獲取。為了減少電池使用,使用設(shè)備無(wú)線通信的所有應(yīng)用的后臺(tái)獲取會(huì)被合并,如果你向系統(tǒng)報(bào)告新數(shù)據(jù)無(wú)法獲取,iOS 會(huì)適應(yīng)并使用此信息避免會(huì)繼續(xù)獲取。
開啟后臺(tái)獲取的第一步是在 info plist 文件中對(duì) UIBackgroundModes 鍵指定特定的值。最簡(jiǎn)單的途徑是在 Xcode 5 的 project editor 中新的 Capabilities 標(biāo)簽頁(yè)中設(shè)置,這個(gè)標(biāo)簽頁(yè)包含了后臺(tái)模式部分,可以方便配置多任務(wù)選項(xiàng)。
http://wiki.jikexueyuan.com/project/objc/images/5-12.jpg" alt="" />
或者,你可以手動(dòng)編輯這個(gè)值
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
接下來(lái),告訴 iOS 多久進(jìn)行一次數(shù)據(jù)獲取
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
return YES;
}
iOS 默認(rèn)不進(jìn)行后臺(tái)獲取,所以你需要設(shè)置一個(gè)時(shí)間間隔,否則,你的應(yīng)用程序永遠(yuǎn)不能在后臺(tái)被喚醒。UIApplicationBackgroundFetchIntervalMinimum 這個(gè)值要求系統(tǒng)盡可能頻繁地去管理你的程序到底什么時(shí)候應(yīng)該被喚醒,但如果你不需要這樣的話,你也應(yīng)該指定一個(gè)你想要的的時(shí)間間隔。例如,一個(gè)天氣的應(yīng)用程序,可能只需要幾個(gè)小時(shí)才更新一次,iOS 將會(huì)在后臺(tái)獲取之間至少等待你指定的時(shí)間間隔。
如果你的應(yīng)用允許用戶退出登錄,那么就沒有獲取新數(shù)據(jù)的需要了,你應(yīng)該把 minimumBackgroundFetchInterval 設(shè)置為 UIApplicationBackgroundFetchIntervalNever,這樣可以節(jié)省資源。
最后一步是在應(yīng)用程序委托中實(shí)現(xiàn)下列方法:
- (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;
}
// 解析響應(yīng)/數(shù)據(jù)以決定新內(nèi)容是否可用
BOOL hasNewData = ...
if (hasNewData) {
completionHandler(UIBackgroundFetchResultNewData);
} else {
completionHandler(UIBackgroundFetchResultNoData);
}
}];
// 開始任務(wù)
[task resume];
}
系統(tǒng)喚醒應(yīng)用程序后將會(huì)執(zhí)行這個(gè)委托方法。需要注意的是,你只有 30 秒的時(shí)間來(lái)確定獲取的新內(nèi)容是否可用,然后處理新內(nèi)容并更新界面。30 秒時(shí)間應(yīng)該足夠去從網(wǎng)絡(luò)獲取數(shù)據(jù)和獲取界面的縮略圖,但是最多只有 30 秒。當(dāng)完成了網(wǎng)絡(luò)請(qǐng)求和更新界面后,你應(yīng)該執(zhí)行完成的回調(diào)。
完成回調(diào)的執(zhí)行有兩個(gè)目的。首先,系統(tǒng)會(huì)估量你的進(jìn)程消耗的電量,并根據(jù)你傳遞的 UIBackgroundFetchResult 參數(shù)記錄新數(shù)據(jù)是否可用。其次,當(dāng)你調(diào)用完成的處理代碼時(shí),應(yīng)用的界面縮略圖會(huì)被采用,并更新應(yīng)用程序切換器。當(dāng)用戶在應(yīng)用間切換時(shí),用戶將會(huì)看到新內(nèi)容。這種通過(guò) completion handler 來(lái)報(bào)告并且生成截圖的方法,在新的多任務(wù)處理 API 中是很常見的。
在實(shí)際應(yīng)用中,你應(yīng)該將 completionHandler 傳遞到應(yīng)用程序的子組件,然后在處理完數(shù)據(jù)和更新界面后調(diào)用。
在這里,你可能想知道 iOS 是如何在應(yīng)用程序后臺(tái)運(yùn)行時(shí)獲得界面截圖的,并且想知道應(yīng)用程序的生命周期與后臺(tái)獲取之間有什么關(guān)系。如果應(yīng)用程序處于掛起狀態(tài),系統(tǒng)會(huì)先喚醒應(yīng)用,然后再調(diào)用 application: performFetchWithCompletionHandler:。如果應(yīng)用程序還沒有啟動(dòng),系統(tǒng)將會(huì)啟動(dòng)它,然后調(diào)用常見的委托方法,包括 application: didFinishLaunchingWithOptions:。你可以把這種應(yīng)用程序運(yùn)行的方式想像為用戶從 Springboard 啟動(dòng)這個(gè)程序,區(qū)別僅僅在于界面是看不見的,在屏幕外渲染的。
大多數(shù)情況下,無(wú)論應(yīng)用在后臺(tái)啟動(dòng)或者在前臺(tái),你會(huì)執(zhí)行相同的工作,但你可以通過(guò)查看 UIApplication 的 applicationState 屬性來(lái)判斷應(yīng)用是不是從后臺(tái)啟動(dòng)。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState);
return YES;
}
有兩種可以模擬后臺(tái)獲取的途徑。最簡(jiǎn)單是從 Xcode 運(yùn)行你的應(yīng)用,當(dāng)應(yīng)用運(yùn)行時(shí),在 Xcode 的 Debug 菜單選擇 Simulate Background Fetch.
第二種方法,使用 scheme 更改 Xcode 運(yùn)行程序的方式。在 Xcode 菜單的 Product 選項(xiàng),選擇 Scheme 然后選擇 Manage Schemes。在這里,你可以編輯或者添加一個(gè)新的 scheme,然后選中 Launch due to a background fetch event 。如下圖:
http://wiki.jikexueyuan.com/project/objc/images/5-13.png" alt="" />
遠(yuǎn)程通知允許你在重要事件發(fā)生時(shí),告知你的應(yīng)用。你可能需要發(fā)送新的即時(shí)信息,突發(fā)新聞的提醒,或者用戶喜愛電視的最新劇集已經(jīng)可以下載以便離線觀看的消息。遠(yuǎn)程通知很適合用于那些偶爾出現(xiàn),但卻很重要的內(nèi)容,如果使用后臺(tái)獲取模式中在兩次獲取間需要等待的時(shí)間是不可接受的話,遠(yuǎn)程通知會(huì)是一個(gè)不錯(cuò)的選擇。遠(yuǎn)程通知會(huì)比后臺(tái)獲取更有效率,因?yàn)閼?yīng)用程序只有在需要的時(shí)候才會(huì)啟動(dòng)。
一條遠(yuǎn)程通知實(shí)際上只是一條普通的帶有 content-available 標(biāo)志的推送通知。你可以發(fā)送一條帶有提醒信息的推送去告訴用戶有事請(qǐng)發(fā)生了,同時(shí)在后臺(tái)對(duì)界面進(jìn)行更新。但遠(yuǎn)程通知也可以做到在安靜地,沒有提醒消息或者任何聲音的情況下,只去更新應(yīng)用界面或者觸發(fā)后臺(tái)工作。然后你可以在完成下載或者處理完新內(nèi)容后,發(fā)送一條本地通知。
靜默的推送通知有速度限制,所以你可以大膽地根據(jù)應(yīng)用程序的需要發(fā)送盡可能多的通知。iOS 和蘋果推送服務(wù)會(huì)控制推送通知多久被遞送,發(fā)送很多推送通知是沒有問(wèn)題的。如果你的推送通知達(dá)到了限制,推送通知可能會(huì)被延遲,直到設(shè)備下次發(fā)送保持活動(dòng)狀態(tài)的數(shù)據(jù)包,或者收到另外一個(gè)通知。
要發(fā)送一條遠(yuǎn)程通知,需要在推送通知的有效負(fù)載(payload)設(shè)置 content-available 標(biāo)志。content-available 標(biāo)志和用來(lái)通知 報(bào)刊應(yīng)用(Newsstand)的健值是一樣的,因此,大多數(shù)推送腳本和庫(kù)都已經(jīng)支持遠(yuǎn)程通知。當(dāng)你發(fā)送一條遠(yuǎn)程通知時(shí),你可能還想要包含一些通知有效負(fù)載中的數(shù)據(jù),讓你應(yīng)用程序可以引用事件。這可以為你節(jié)省一些網(wǎng)絡(luò)請(qǐng)求,并提高應(yīng)用程序的響應(yīng)度。
我建議在開發(fā)的時(shí)候,使用 Nomad CLI’s Houston 工具發(fā)送推送消息,當(dāng)然你也可以使用你喜歡的庫(kù)或腳本。
你可以通過(guò) nomad-cli ruby gem 來(lái)安裝 Houston
gem install nomad-cli
然后通過(guò)包含在 Nomad 的 apn 實(shí)用工具發(fā)送一條通知:
# Send a Push Notification to your Device
apn push <device token> -c /path/to/key-cert.pem -n -d content-id=42
在這里,-n 標(biāo)志指定應(yīng)該包含 content-available 健值,-d 標(biāo)志允許添加我們自定義的數(shù)據(jù)健值到有效負(fù)荷。
通知的有效負(fù)荷(payload)結(jié)果和下面類似:
{
"aps" : {
"content-available" : 1
},
"content-id" : 42
}
iOS 7 添加了一個(gè)新的應(yīng)用程序委托方法,當(dāng)接收到一條帶有 content-available 的推送通知時(shí),下面的方法會(huì)被調(diào)用:
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(@"Remote Notification userInfo is %@", userInfo);
NSNumber *contentID = userInfo[@"content-id"];
// 根據(jù) content ID 進(jìn)行操作
completionHandler(UIBackgroundFetchResultNewData);
}
和后臺(tái)抓取一樣,應(yīng)用程序進(jìn)入后臺(tái)啟動(dòng),也有 30 秒的時(shí)間去獲取新內(nèi)容并更新界面,最后調(diào)用完成的處理代碼。我們可以像后臺(tái)獲取那樣,執(zhí)行快速的網(wǎng)絡(luò)請(qǐng)求,但我們可以使用新的強(qiáng)大的后臺(tái)傳輸服務(wù),處理任務(wù)隊(duì)列,下面看看我們?nèi)绾卧谌蝿?wù)完成后更新界面。
NSURLSession 是 iOS 7 添加的一個(gè)新類,它也是 Foundation networking 中的新技術(shù)。作為 NSURLConnection 的替代品,一些熟悉的概念和類都保留下來(lái)了,例如 NSURL,NSURLRequest 和 NSURLResponse。所以,你可以使用 NSURLSessionTask 這一 NSURLConnection 的替代品,來(lái)處理網(wǎng)絡(luò)請(qǐng)求及響應(yīng)。一共有 3 種會(huì)話任務(wù):數(shù)據(jù),下載和上傳。每一種都向 NSURLSessionTask 添加了語(yǔ)法糖,根據(jù)你的需要,適當(dāng)選擇一種。
一個(gè) NSURLSession 對(duì)象協(xié)調(diào)一個(gè)或多個(gè) NSURLSessionTask 對(duì)象,并根據(jù) NSURLSessionTask 創(chuàng)建的 NSURLSessionConfiguration 實(shí)現(xiàn)不同的功能。使用相同的配置,你也可以創(chuàng)建多組具有相關(guān)任務(wù)的 NSURLSession 對(duì)象。要利用后臺(tái)傳輸服務(wù),你將會(huì)使用 [NSURLSessionConfiguration backgroundSessionConfiguration] 來(lái)創(chuàng)建一個(gè)會(huì)話配置。添加到后臺(tái)會(huì)話的任務(wù)在外部進(jìn)程運(yùn)行,即使應(yīng)用程序被掛起,崩潰,或者被殺死,它依然會(huì)運(yùn)行。
NSURLSessionConfiguration 允許你設(shè)置默認(rèn)的 HTTP 頭,配置緩存策略,限制使用蜂窩數(shù)據(jù)等等。其中一個(gè)選項(xiàng)是 discretionary 標(biāo)志,這個(gè)標(biāo)志允許系統(tǒng)為分配任務(wù)進(jìn)行性能優(yōu)化。這意味著只有當(dāng)設(shè)備有足夠電量時(shí),設(shè)備才通過(guò) Wifi 進(jìn)行數(shù)據(jù)傳輸。如果電量低,或者只僅有一個(gè)蜂窩連接,傳輸任務(wù)是不會(huì)運(yùn)行的。后臺(tái)傳輸總是在 discretionary 模式下運(yùn)行。
目前為止,我們大概了解了 NSURLSession,以及一個(gè)后臺(tái)會(huì)話如何進(jìn)行,接下來(lái),讓我們回到遠(yuǎn)程通知的例子,添加一些代碼來(lái)處理后臺(tái)傳輸服務(wù)的下載隊(duì)列。當(dāng)下載完成后,我們會(huì)通知用戶該文件已經(jīng)可以使用了。
首先,我們先處理一條遠(yuǎn)程通知,并把一個(gè) NSURLSessionDownloadTask 添加到后臺(tái)傳輸服務(wù)的隊(duì)列。在 backgroundURLSession 方法中,我們根據(jù)后臺(tái)會(huì)話配置,創(chuàng)建一個(gè) NSURLSession 對(duì)象,并把 application delegate 作為會(huì)話的委托對(duì)象。文檔不建議對(duì)于相同的標(biāo)識(shí)符 (identifier) 創(chuàng)建多個(gè)會(huì)話對(duì)象,所以我們使用 dispatch_once 來(lái)避免潛在的問(wèn)題:
- (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);
}
我們使用 NSURLSession 類方法創(chuàng)建一個(gè)下載任務(wù),配置請(qǐng)求,并提供說(shuō)明供以后使用。因?yàn)樗袝?huì)話任務(wù)一開始處于掛起狀態(tài),你必須謹(jǐn)記要調(diào)用 [task resume] 保證開始了任務(wù)。
現(xiàn)在,我們需要實(shí)現(xiàn) NSURLSessionDownloadDelegate 的委托方法,當(dāng)下載完成時(shí),調(diào)用回調(diào)函數(shù)。如果你需要處理認(rèn)證或會(huì)話生命周期的其他事件,你可能還需要實(shí)現(xiàn) NSURLSessionDelegate 或 NSURLSessionTaskDelegate 的方法。你應(yīng)該閱讀 Apple 的 Life Cycle of a URL Session with Custom Delegates 文檔,它講解了所有類型的會(huì)話任務(wù)的完整生命周期。
NSURLSessionDownloadDelegate 中的委托方法全部是必須實(shí)現(xiàn)的,盡管在這個(gè)例子中我們只需要用到 [NSURLSession downloadTask:didFinishDownloadingToURL:]。任務(wù)完成下載時(shí),你會(huì)得到一個(gè)磁盤上該文件的臨時(shí) URL。你必須把這個(gè)文件移動(dòng)或復(fù)制你的應(yīng)用程序空間,因?yàn)楫?dāng)你從這個(gè)委托方法返回時(shí),該文件將從臨時(shí)存儲(chǔ)中刪除。
#Pragma Mark - NSURLSessionDownloadDelegate
- (void) URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location);
// 用 NSFileManager 將文件復(fù)制到應(yīng)用的存儲(chǔ)中
// ...
// 通知 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
{
}
當(dāng)后臺(tái)會(huì)話任務(wù)完成時(shí),如果你的應(yīng)用程序仍然在前臺(tái)運(yùn)行,上面的代碼已經(jīng)足夠了。然而,在大多數(shù)情況下,你的應(yīng)用程序可能是沒有運(yùn)行的,或者在后臺(tái)被掛起。在這些情況下,你必須實(shí)現(xiàn)應(yīng)用程序委托的兩個(gè)方法,這樣系統(tǒng)就可以喚醒你的應(yīng)用程序。不同于以往的委托回調(diào),該應(yīng)用程序委托會(huì)被調(diào)用兩次,因?yàn)槟臅?huì)話和任務(wù)委托可能會(huì)收到一系列消息。app delegate 的:handleEventsForBackgroundURLSession: 方法會(huì)在這些 NSURLSession 委托的消息發(fā)送前被調(diào)用,然后,URLSessionDidFinishEventsForBackgroundURLSession 在隨后被調(diào)用。在前面的方法中,包含了一個(gè)后臺(tái)完成的回調(diào)(completionHandler),并在后面的方法中執(zhí)行回調(diào)以便更新界面:
- (void) application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
// 你必須重新建立一個(gè)后臺(tái) seesiong 的參照
// 否則 NSURLSessionDownloadDelegate 和 NSURLSessionDelegate 方法會(huì)因?yàn)? // 沒有 對(duì) session 的 delegate 設(shè)定而不會(huì)被調(diào)用。參見上面的 backgroundURLSession
NSURLSession *backgroundSession = [self backgroundURLSession];
NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession);
// 保存 completion handler 以在處理 session 事件后更新 UI
[self addCompletionHandler:completionHandler forSession:identifier];
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
NSLog(@"Background URL session %@ finished events.\n", session);
if (session.configuration.identifier) {
// 調(diào)用在 -application:handleEventsForBackgroundURLSession: 中保存的 handler
[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();
}
}
如果當(dāng)后臺(tái)傳輸完成時(shí),應(yīng)用程序不再停留在前臺(tái),那么,對(duì)于更新程序界面來(lái)說(shuō),這個(gè)兩步處理過(guò)程是必要的。此外,如果當(dāng)后臺(tái)傳輸完成時(shí),應(yīng)用程序根本沒有在運(yùn)行,iOS 將會(huì)在后臺(tái)啟動(dòng)該應(yīng)用程序,然后前面的應(yīng)用程序和會(huì)話的委托方法會(huì)在 application:didFinishLaunchingWithOptions: 方法被調(diào)用之后被調(diào)用。
我們簡(jiǎn)單地體驗(yàn)了后臺(tái)傳輸?shù)膹?qiáng)大之處,但你應(yīng)該深入文檔,閱讀 NSURLSessionConfiguration 部分,以便最好地滿足你的使用場(chǎng)景。例如,NSURLSessionTasks 通過(guò) NSURLSessionConfiguration 的 timeoutIntervalForResource 屬性,支持資源超時(shí)特性。你可以使用這個(gè)特性指定你允許完成一個(gè)傳輸所需的最長(zhǎng)時(shí)間。內(nèi)容只在有限的時(shí)間可用,或者在用戶只有有限 Wifi 帶寬的時(shí)間內(nèi)無(wú)法下載或上傳資源的情況下,你也可以使用這個(gè)特性。
除了下載任務(wù),NSURLSession 也全面支持上傳任務(wù),因此,你可能會(huì)在后臺(tái)將視頻上傳到服務(wù)器,這保證用戶不需要再像 iOS 6 那樣保持應(yīng)用程序前臺(tái)運(yùn)行。如果當(dāng)傳輸完成時(shí)你的應(yīng)用程序不需要在后臺(tái)運(yùn)行,一個(gè)比較好的做法是,把 NSURLSessionConfiguration 的 sessionSendsLaunchEvents 屬性設(shè)置為 NO。高效利用系統(tǒng)資源,是一件讓 iOS 和用戶都高興的事。
最后,我們來(lái)說(shuō)一說(shuō)使用后臺(tái)會(huì)話的幾個(gè)限制。作為一個(gè)必須實(shí)現(xiàn)的委托,您不能對(duì) NSURLSession 使用簡(jiǎn)單的基于 block 的回調(diào)方法。后臺(tái)啟動(dòng)應(yīng)用程序,是相對(duì)耗費(fèi)較多資源的,所以總是采用 HTTP 重定向。后臺(tái)傳輸服務(wù)只支持 HTTP 和 HTTPS,你不能使用自定義的協(xié)議。系統(tǒng)會(huì)根據(jù)可用的資源進(jìn)行優(yōu)化,在任何時(shí)候你都不能強(qiáng)制傳輸任務(wù)在后臺(tái)進(jìn)行。
另外,要注意的是在后臺(tái)會(huì)話中,NSURLSessionDataTasks 是完全不支持的,你應(yīng)該只出于短期的,小請(qǐng)求為目的使用這些任務(wù),而不是用來(lái)下載或上傳。
iOS 7 中強(qiáng)大的多任務(wù)和網(wǎng)絡(luò) API 為現(xiàn)有應(yīng)用和新應(yīng)用開啟了一系列全新的可能性。如果你的應(yīng)用程序可以從進(jìn)程外的網(wǎng)絡(luò)傳輸和數(shù)據(jù)中獲益,那么盡情地使用這些美妙的 API。一般情況下,你可以就像你的應(yīng)用正在前臺(tái)運(yùn)行那樣去實(shí)現(xiàn)后臺(tái)傳輸,并進(jìn)行適當(dāng)?shù)慕缑娓拢@里絕大多數(shù)的工作都已經(jīng)為你完成了。