在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ iOS 7 的多任務(wù)
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動畫
常見的后臺實(shí)踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

iOS 7 的多任務(wù)

在 iOS 7 之前,當(dāng)程序置于后臺之后開發(fā)者們對他們程序所能做的事情非常有限。除了 VOIP 和基于地理位置特性以外,唯一能做的地方就是使用后臺任務(wù)(background tasks)讓代碼可以執(zhí)行幾分鐘。如果你想下載比較大的視頻文件以便離線瀏覽,亦或者備份用戶的照片到你的服務(wù)器上,你都僅能完成一部分工作。

iOS 7 添加了兩個新的 API 以便你的程序可以在后臺更新界面以及內(nèi)容。首先是后臺獲?。˙ackground Fetch),它允許你定期地從網(wǎng)絡(luò)獲取新的內(nèi)容。第二個 API 就是遠(yuǎn)程通知(Remote Notifications),這是一個當(dāng)事件發(fā)生時可以讓推送通知主動提醒應(yīng)用的新特性,這兩者都為你的應(yīng)用界面保持最新提供了極大的幫助。在新的后臺傳輸服務(wù) (Background Transfer Service) 中執(zhí)行定期的任務(wù),也允許你在進(jìn)程之外可以執(zhí)行網(wǎng)絡(luò)傳輸(下載和上傳)工作。

后臺獲取 (Background Fetch) 和遠(yuǎn)程通知 (Remote Notification) 基于簡單的 ApplicationDelegate 鉤子,在應(yīng)用程序掛起之前的 30 秒時鐘時間執(zhí)行工作。它們不是用于 CPU 頻繁工作或者長時間運(yùn)行任務(wù),而是用來處理長時間運(yùn)行的網(wǎng)絡(luò)請求隊(duì)列,例如下載一部很大的電影,或者執(zhí)行快速的內(nèi)容更新。

對用戶來說,多任務(wù)處理有一點(diǎn)顯而易見的改變就是新的應(yīng)用切換程序 (the new app switcher),它用來呈現(xiàn)應(yīng)用到后臺時的界面快照。這些快照的存在是有一定理由的--現(xiàn)在你可以在后臺完成工作后更新程序快照,以用來呈現(xiàn)新的內(nèi)容。社交網(wǎng)絡(luò)、新聞或者天氣等應(yīng)用現(xiàn)在都可以直接呈現(xiàn)最新的內(nèi)容而不需要用戶重新打開應(yīng)用。我們稍后會介紹如何更新屏幕快照。

后臺獲取

后臺獲取是一種智能的輪詢機(jī)制,它很適合需要經(jīng)常更新內(nèi)容的程序,像社交網(wǎng)絡(luò),新聞或天氣的程序。為了在用戶啟動程序前提前觸發(fā)后臺獲取,系統(tǒng)會根據(jù)用戶行為喚醒應(yīng)用程序。舉個例子,如果用戶經(jīng)常在下午 1 點(diǎn)使用某個應(yīng)用程序,系統(tǒng)會學(xué)習(xí),適應(yīng)并在使用周期前執(zhí)行后臺獲取。為了減少電池使用,使用設(shè)備無線通信的所有應(yīng)用的后臺獲取會被合并,如果你向系統(tǒng)報告新數(shù)據(jù)無法獲取,iOS 會適應(yīng)并使用此信息避免會繼續(xù)獲取。

開啟后臺獲取的第一步是在 info plist 文件中對 UIBackgroundModes 鍵指定特定的值。最簡單的途徑是在 Xcode 5 的 project editor 中新的 Capabilities 標(biāo)簽頁中設(shè)置,這個標(biāo)簽頁包含了后臺模式部分,可以方便配置多任務(wù)選項(xiàng)。

http://wiki.jikexueyuan.com/project/objc/images/5-12.jpg" alt="" />

或者,你可以手動編輯這個值

<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
</array>  

接下來,告訴 iOS 多久進(jìn)行一次數(shù)據(jù)獲取

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];

    return YES;
}

iOS 默認(rèn)不進(jìn)行后臺獲取,所以你需要設(shè)置一個時間間隔,否則,你的應(yīng)用程序永遠(yuǎn)不能在后臺被喚醒。UIApplicationBackgroundFetchIntervalMinimum 這個值要求系統(tǒng)盡可能頻繁地去管理你的程序到底什么時候應(yīng)該被喚醒,但如果你不需要這樣的話,你也應(yīng)該指定一個你想要的的時間間隔。例如,一個天氣的應(yīng)用程序,可能只需要幾個小時才更新一次,iOS 將會在后臺獲取之間至少等待你指定的時間間隔。

如果你的應(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)用程序后將會執(zhí)行這個委托方法。需要注意的是,你只有 30 秒的時間來確定獲取的新內(nèi)容是否可用,然后處理新內(nèi)容并更新界面。30 秒時間應(yīng)該足夠去從網(wǎng)絡(luò)獲取數(shù)據(jù)和獲取界面的縮略圖,但是最多只有 30 秒。當(dāng)完成了網(wǎng)絡(luò)請求和更新界面后,你應(yīng)該執(zhí)行完成的回調(diào)。

完成回調(diào)的執(zhí)行有兩個目的。首先,系統(tǒng)會估量你的進(jìn)程消耗的電量,并根據(jù)你傳遞的 UIBackgroundFetchResult 參數(shù)記錄新數(shù)據(jù)是否可用。其次,當(dāng)你調(diào)用完成的處理代碼時,應(yīng)用的界面縮略圖會被采用,并更新應(yīng)用程序切換器。當(dāng)用戶在應(yīng)用間切換時,用戶將會看到新內(nèi)容。這種通過 completion handler 來報告并且生成截圖的方法,在新的多任務(wù)處理 API 中是很常見的。

在實(shí)際應(yīng)用中,你應(yīng)該將 completionHandler 傳遞到應(yīng)用程序的子組件,然后在處理完數(shù)據(jù)和更新界面后調(diào)用。

在這里,你可能想知道 iOS 是如何在應(yīng)用程序后臺運(yùn)行時獲得界面截圖的,并且想知道應(yīng)用程序的生命周期與后臺獲取之間有什么關(guān)系。如果應(yīng)用程序處于掛起狀態(tài),系統(tǒng)會先喚醒應(yīng)用,然后再調(diào)用 application: performFetchWithCompletionHandler:。如果應(yīng)用程序還沒有啟動,系統(tǒng)將會啟動它,然后調(diào)用常見的委托方法,包括 application: didFinishLaunchingWithOptions:。你可以把這種應(yīng)用程序運(yùn)行的方式想像為用戶從 Springboard 啟動這個程序,區(qū)別僅僅在于界面是看不見的,在屏幕外渲染的。

大多數(shù)情況下,無論應(yīng)用在后臺啟動或者在前臺,你會執(zhí)行相同的工作,但你可以通過查看 UIApplication 的 applicationState 屬性來判斷應(yīng)用是不是從后臺啟動。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState);

    return YES;
}

測試后臺數(shù)據(jù)獲取

有兩種可以模擬后臺獲取的途徑。最簡單是從 Xcode 運(yùn)行你的應(yīng)用,當(dāng)應(yīng)用運(yùn)行時,在 Xcode 的 Debug 菜單選擇 Simulate Background Fetch.

第二種方法,使用 scheme 更改 Xcode 運(yùn)行程序的方式。在 Xcode 菜單的 Product 選項(xiàng),選擇 Scheme 然后選擇 Manage Schemes。在這里,你可以編輯或者添加一個新的 scheme,然后選中 Launch due to a background fetch event 。如下圖:

http://wiki.jikexueyuan.com/project/objc/images/5-13.png" alt="" />

遠(yuǎn)程通知

遠(yuǎn)程通知允許你在重要事件發(fā)生時,告知你的應(yīng)用。你可能需要發(fā)送新的即時信息,突發(fā)新聞的提醒,或者用戶喜愛電視的最新劇集已經(jīng)可以下載以便離線觀看的消息。遠(yuǎn)程通知很適合用于那些偶爾出現(xiàn),但卻很重要的內(nèi)容,如果使用后臺獲取模式中在兩次獲取間需要等待的時間是不可接受的話,遠(yuǎn)程通知會是一個不錯的選擇。遠(yuǎn)程通知會比后臺獲取更有效率,因?yàn)閼?yīng)用程序只有在需要的時候才會啟動。

一條遠(yuǎn)程通知實(shí)際上只是一條普通的帶有 content-available 標(biāo)志的推送通知。你可以發(fā)送一條帶有提醒信息的推送去告訴用戶有事請發(fā)生了,同時在后臺對界面進(jìn)行更新。但遠(yuǎn)程通知也可以做到在安靜地,沒有提醒消息或者任何聲音的情況下,只去更新應(yīng)用界面或者觸發(fā)后臺工作。然后你可以在完成下載或者處理完新內(nèi)容后,發(fā)送一條本地通知。

靜默的推送通知有速度限制,所以你可以大膽地根據(jù)應(yīng)用程序的需要發(fā)送盡可能多的通知。iOS 和蘋果推送服務(wù)會控制推送通知多久被遞送,發(fā)送很多推送通知是沒有問題的。如果你的推送通知達(dá)到了限制,推送通知可能會被延遲,直到設(shè)備下次發(fā)送保持活動狀態(tài)的數(shù)據(jù)包,或者收到另外一個通知。

發(fā)送遠(yuǎn)程通知

要發(fā)送一條遠(yuǎn)程通知,需要在推送通知的有效負(fù)載(payload)設(shè)置 content-available 標(biāo)志。content-available 標(biāo)志和用來通知 報刊應(yīng)用(Newsstand)的健值是一樣的,因此,大多數(shù)推送腳本和庫都已經(jīng)支持遠(yuǎn)程通知。當(dāng)你發(fā)送一條遠(yuǎn)程通知時,你可能還想要包含一些通知有效負(fù)載中的數(shù)據(jù),讓你應(yīng)用程序可以引用事件。這可以為你節(jié)省一些網(wǎng)絡(luò)請求,并提高應(yīng)用程序的響應(yīng)度。

我建議在開發(fā)的時候,使用 Nomad CLI’s Houston 工具發(fā)送推送消息,當(dāng)然你也可以使用你喜歡的庫或腳本。

你可以通過 nomad-cli ruby gem 來安裝 Houston

gem install nomad-cli

然后通過包含在 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 添加了一個新的應(yīng)用程序委托方法,當(dāng)接收到一條帶有 content-available 的推送通知時,下面的方法會被調(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);
}

和后臺抓取一樣,應(yīng)用程序進(jìn)入后臺啟動,也有 30 秒的時間去獲取新內(nèi)容并更新界面,最后調(diào)用完成的處理代碼。我們可以像后臺獲取那樣,執(zhí)行快速的網(wǎng)絡(luò)請求,但我們可以使用新的強(qiáng)大的后臺傳輸服務(wù),處理任務(wù)隊(duì)列,下面看看我們?nèi)绾卧谌蝿?wù)完成后更新界面。

NSURLSession 和 后臺傳輸服務(wù)(Background Transfer Service)

NSURLSession 是 iOS 7 添加的一個新類,它也是 Foundation networking 中的新技術(shù)。作為 NSURLConnection 的替代品,一些熟悉的概念和類都保留下來了,例如 NSURL,NSURLRequestNSURLResponse。所以,你可以使用 NSURLSessionTask 這一 NSURLConnection 的替代品,來處理網(wǎng)絡(luò)請求及響應(yīng)。一共有 3 種會話任務(wù):數(shù)據(jù),下載和上傳。每一種都向 NSURLSessionTask 添加了語法糖,根據(jù)你的需要,適當(dāng)選擇一種。

一個 NSURLSession 對象協(xié)調(diào)一個或多個 NSURLSessionTask 對象,并根據(jù) NSURLSessionTask 創(chuàng)建的 NSURLSessionConfiguration 實(shí)現(xiàn)不同的功能。使用相同的配置,你也可以創(chuàng)建多組具有相關(guān)任務(wù)的 NSURLSession 對象。要利用后臺傳輸服務(wù),你將會使用 [NSURLSessionConfiguration backgroundSessionConfiguration] 來創(chuàng)建一個會話配置。添加到后臺會話的任務(wù)在外部進(jìn)程運(yùn)行,即使應(yīng)用程序被掛起,崩潰,或者被殺死,它依然會運(yùn)行。

NSURLSessionConfiguration 允許你設(shè)置默認(rèn)的 HTTP 頭,配置緩存策略,限制使用蜂窩數(shù)據(jù)等等。其中一個選項(xiàng)是 discretionary 標(biāo)志,這個標(biāo)志允許系統(tǒng)為分配任務(wù)進(jìn)行性能優(yōu)化。這意味著只有當(dāng)設(shè)備有足夠電量時,設(shè)備才通過 Wifi 進(jìn)行數(shù)據(jù)傳輸。如果電量低,或者只僅有一個蜂窩連接,傳輸任務(wù)是不會運(yùn)行的。后臺傳輸總是在 discretionary 模式下運(yùn)行。

目前為止,我們大概了解了 NSURLSession,以及一個后臺會話如何進(jìn)行,接下來,讓我們回到遠(yuǎn)程通知的例子,添加一些代碼來處理后臺傳輸服務(wù)的下載隊(duì)列。當(dāng)下載完成后,我們會通知用戶該文件已經(jīng)可以使用了。

NSURLSessionDownloadTask

首先,我們先處理一條遠(yuǎn)程通知,并把一個 NSURLSessionDownloadTask 添加到后臺傳輸服務(wù)的隊(duì)列。在 backgroundURLSession 方法中,我們根據(jù)后臺會話配置,創(chuàng)建一個 NSURLSession 對象,并把 application delegate 作為會話的委托對象。文檔不建議對于相同的標(biāo)識符 (identifier) 創(chuàng)建多個會話對象,所以我們使用 dispatch_once 來避免潛在的問題:

- (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)建一個下載任務(wù),配置請求,并提供說明供以后使用。因?yàn)樗袝捜蝿?wù)一開始處于掛起狀態(tài),你必須謹(jǐn)記要調(diào)用 [task resume] 保證開始了任務(wù)。

現(xiàn)在,我們需要實(shí)現(xiàn) NSURLSessionDownloadDelegate 的委托方法,當(dāng)下載完成時,調(diào)用回調(diào)函數(shù)。如果你需要處理認(rèn)證或會話生命周期的其他事件,你可能還需要實(shí)現(xiàn) NSURLSessionDelegateNSURLSessionTaskDelegate 的方法。你應(yīng)該閱讀 Apple 的 Life Cycle of a URL Session with Custom Delegates 文檔,它講解了所有類型的會話任務(wù)的完整生命周期。

NSURLSessionDownloadDelegate 中的委托方法全部是必須實(shí)現(xiàn)的,盡管在這個例子中我們只需要用到 [NSURLSession downloadTask:didFinishDownloadingToURL:]。任務(wù)完成下載時,你會得到一個磁盤上該文件的臨時 URL。你必須把這個文件移動或復(fù)制你的應(yīng)用程序空間,因?yàn)楫?dāng)你從這個委托方法返回時,該文件將從臨時存儲中刪除。

#Pragma Mark - NSURLSessionDownloadDelegate

- (void)         URLSession:(NSURLSession *)session 
               downloadTask:(NSURLSessionDownloadTask *)downloadTask
  didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location);

    // 用 NSFileManager 將文件復(fù)制到應(yīng)用的存儲中
    // ...

    // 通知 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)后臺會話任務(wù)完成時,如果你的應(yīng)用程序仍然在前臺運(yùn)行,上面的代碼已經(jīng)足夠了。然而,在大多數(shù)情況下,你的應(yīng)用程序可能是沒有運(yùn)行的,或者在后臺被掛起。在這些情況下,你必須實(shí)現(xiàn)應(yīng)用程序委托的兩個方法,這樣系統(tǒng)就可以喚醒你的應(yīng)用程序。不同于以往的委托回調(diào),該應(yīng)用程序委托會被調(diào)用兩次,因?yàn)槟臅捄腿蝿?wù)委托可能會收到一系列消息。app delegate 的:handleEventsForBackgroundURLSession: 方法會在這些 NSURLSession 委托的消息發(fā)送前被調(diào)用,然后,URLSessionDidFinishEventsForBackgroundURLSession 在隨后被調(diào)用。在前面的方法中,包含了一個后臺完成的回調(diào)(completionHandler),并在后面的方法中執(zhí)行回調(diào)以便更新界面:

- (void)                  application:(UIApplication *)application 
  handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
    // 你必須重新建立一個后臺 seesiong 的參照
    // 否則 NSURLSessionDownloadDelegate 和 NSURLSessionDelegate 方法會因?yàn)?    // 沒有 對 session 的 delegate 設(shè)定而不會被調(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)后臺傳輸完成時,應(yīng)用程序不再停留在前臺,那么,對于更新程序界面來說,這個兩步處理過程是必要的。此外,如果當(dāng)后臺傳輸完成時,應(yīng)用程序根本沒有在運(yùn)行,iOS 將會在后臺啟動該應(yīng)用程序,然后前面的應(yīng)用程序和會話的委托方法會在 application:didFinishLaunchingWithOptions: 方法被調(diào)用之后被調(diào)用。

配置和限制

我們簡單地體驗(yàn)了后臺傳輸?shù)膹?qiáng)大之處,但你應(yīng)該深入文檔,閱讀 NSURLSessionConfiguration 部分,以便最好地滿足你的使用場景。例如,NSURLSessionTasks 通過 NSURLSessionConfigurationtimeoutIntervalForResource 屬性,支持資源超時特性。你可以使用這個特性指定你允許完成一個傳輸所需的最長時間。內(nèi)容只在有限的時間可用,或者在用戶只有有限 Wifi 帶寬的時間內(nèi)無法下載或上傳資源的情況下,你也可以使用這個特性。

除了下載任務(wù),NSURLSession 也全面支持上傳任務(wù),因此,你可能會在后臺將視頻上傳到服務(wù)器,這保證用戶不需要再像 iOS 6 那樣保持應(yīng)用程序前臺運(yùn)行。如果當(dāng)傳輸完成時你的應(yīng)用程序不需要在后臺運(yùn)行,一個比較好的做法是,把 NSURLSessionConfigurationsessionSendsLaunchEvents 屬性設(shè)置為 NO。高效利用系統(tǒng)資源,是一件讓 iOS 和用戶都高興的事。

最后,我們來說一說使用后臺會話的幾個限制。作為一個必須實(shí)現(xiàn)的委托,您不能對 NSURLSession 使用簡單的基于 block 的回調(diào)方法。后臺啟動應(yīng)用程序,是相對耗費(fèi)較多資源的,所以總是采用 HTTP 重定向。后臺傳輸服務(wù)只支持 HTTP 和 HTTPS,你不能使用自定義的協(xié)議。系統(tǒng)會根據(jù)可用的資源進(jìn)行優(yōu)化,在任何時候你都不能強(qiáng)制傳輸任務(wù)在后臺進(jìn)行。

另外,要注意的是在后臺會話中,NSURLSessionDataTasks 是完全不支持的,你應(yīng)該只出于短期的,小請求為目的使用這些任務(wù),而不是用來下載或上傳。

總結(jié)

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)用正在前臺運(yùn)行那樣去實(shí)現(xiàn)后臺傳輸,并進(jìn)行適當(dāng)?shù)慕缑娓拢@里絕大多數(shù)的工作都已經(jīng)為你完成了。

  • 使用適當(dāng)?shù)男?API 來為你的應(yīng)用程序提供內(nèi)容。
  • 盡可能早地調(diào)用 completion handler 以提高效率。
  • 讓 completion handler 為應(yīng)用程序更新界面快照。

擴(kuò)展閱讀

上一篇:Core Data 概述下一篇:調(diào)試