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

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

iOS 7 的多任務(wù)

在 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)獲取

后臺(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;
}

測(cè)試后臺(tái)數(shù)據(jù)獲取

有兩種可以模擬后臺(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)程通知

遠(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ā)送一條遠(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 和 后臺(tái)傳輸服務(wù)(Background Transfer Service)

NSURLSession 是 iOS 7 添加的一個(gè)新類,它也是 Foundation networking 中的新技術(shù)。作為 NSURLConnection 的替代品,一些熟悉的概念和類都保留下來(lái)了,例如 NSURL,NSURLRequestNSURLResponse。所以,你可以使用 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)可以使用了。

NSURLSessionDownloadTask

首先,我們先處理一條遠(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) NSURLSessionDelegateNSURLSessionTaskDelegate 的方法。你應(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ò) NSURLSessionConfigurationtimeoutIntervalForResource 屬性,支持資源超時(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è)比較好的做法是,把 NSURLSessionConfigurationsessionSendsLaunchEvents 屬性設(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)下載或上傳。

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

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

擴(kuò)展閱讀

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