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

鍍金池/ 教程/ iOS/ 精通 iCloud 文檔存儲(chǔ)
與四軸無人機(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 上捕獲視頻
四軸無人機(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é)無止境
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ī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

精通 iCloud 文檔存儲(chǔ)

即便在推出 3 年后,iCloud 文檔存儲(chǔ)依然是一個(gè)充滿神秘、誤解和抱怨的話題。iCloud 同步經(jīng)常被批評(píng)不可靠且速度慢。雖然在 iCloud 的早期有一些嚴(yán)重的 bug,開發(fā)者們還是不得不學(xué)習(xí)有關(guān)文件同步的課程。文件同步事關(guān)重大,為應(yīng)用開發(fā)帶來了新方向 -- 一個(gè)經(jīng)常被低估的方向,比如進(jìn)行同步服務(wù)相關(guān)的合作時(shí),對(duì)于處理文件異步更改的需要。

本文會(huì)介紹幾個(gè)創(chuàng)建支持 iCloud 的應(yīng)用時(shí)可能會(huì)遇到的一些絆腳石。因?yàn)楸疚闹粫?huì)給出一些粗略的概述,所以如果你對(duì) iCloud 文檔存儲(chǔ)還不熟悉,我們強(qiáng)烈建議你先閱讀 Apple iCloud companion guide

文檔存儲(chǔ)簡(jiǎn)介

iCloud 文檔存儲(chǔ)的核心思想非常簡(jiǎn)單:每個(gè)應(yīng)用都有至少通往一個(gè)“魔法文件夾”的入口,該文件夾可以存儲(chǔ)文件并且隨后在所有注冊(cè)了同一個(gè) iCloud 帳號(hào)的設(shè)備間同步。

與其他基于文件系統(tǒng)的同步服務(wù)相比,iCloud 文檔存儲(chǔ)得益于與 OS X 和 iOS 的深度整合。很多系統(tǒng)框架已經(jīng)被擴(kuò)展以支持 iCloud。像 NSDocumentUIDocument 這樣的類被按照可以處理外部變化來進(jìn)行設(shè)計(jì)。版本存儲(chǔ)和 NSFileVersion 處理同步?jīng)_突。Spotlight 被用來提供同步元數(shù)據(jù),比如文件傳輸進(jìn)度或者云端文檔可用性。

寫一個(gè)簡(jiǎn)單的基于文檔并開啟了 iCloud 的 OS X 應(yīng)用并不需要費(fèi)多大力氣。實(shí)際上你并不需要關(guān)心任何 iCloud 內(nèi)部的工作,NSDocument 無償?shù)淖隽藥缀趺考虑椋簠f(xié)調(diào)文檔的 iCloud 訪問,自動(dòng)觀察外部變化,觸發(fā)下載,處理沖突。它甚至提供了一個(gè)簡(jiǎn)單的 UI 界面來管理云文檔。你需要做的所有事情就是創(chuàng)建一個(gè) NSDocument 子類并實(shí)現(xiàn)讀取和寫入文檔內(nèi)容所需要的方法。

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

然而,一旦脫離預(yù)設(shè)的路徑,你就需要了解的更多。例如,默認(rèn)打開面板提供的單層文件夾以外的任何操作都需要手動(dòng)完成??赡苣愕膽?yīng)用需要管理除了文檔內(nèi)容以外的文檔,比如像 Mail,iPhoto 或者 Ulysses (我們自己的app) 中做的那樣。這種時(shí)候,你不能依賴于 NSDocument,而需要自己實(shí)現(xiàn)它的功能。但為此你需要對(duì) iCloud 提供的鎖和通知機(jī)制有一個(gè)深入的了解。

開發(fā)支持 iCloud 的 iOS 應(yīng)用同樣需要更多的工作和知識(shí);雖然 UIDocument 仍然管理 iCloud 文件訪問和處理同步?jīng)_突,但缺乏管理文檔和文件夾的圖形界面。因?yàn)樾阅芎痛鎯?chǔ)空間的原因,iOS 也不會(huì)自動(dòng)從云端下載新文檔。你需要使用 Spotlight 來檢索最近變化的目錄并手動(dòng)觸發(fā)下載。

什么是開放性容器 (Ubiquity Container)

任何符合 App Store 條件的應(yīng)用都可以使用 iCloud 文檔存儲(chǔ)。設(shè)置正確的授權(quán)后,就獲得了一個(gè)或多個(gè)所謂的“開放性容器”的訪問權(quán)限。這是蘋果用來稱呼“一個(gè)被 iCloud 管理和同步的目錄”的別稱。每一個(gè)開放性容器限定在一個(gè) app id 內(nèi),由此讓每個(gè)用戶在每個(gè)應(yīng)用中有一份共享的存儲(chǔ)倉(cāng)庫(kù)。有多個(gè)應(yīng)用的開發(fā)者可以指定同一個(gè)團(tuán)隊(duì)的多個(gè) app id,由此可以訪問多個(gè)容器。

NSFileManager 通過 URLForUbiquityContainerIdentifier: 提供每一個(gè)容器的 URL。在 OS X 系統(tǒng),可以通過打開 ~/Library/Mobile Documents 目錄來查看所有可用的開放性容器。

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

通常每個(gè)開放性容器有兩個(gè)并發(fā)進(jìn)程訪問。首先,有一個(gè)應(yīng)用呈現(xiàn)和操作容器內(nèi)的文檔。第二,有一個(gè)主要通過開放性守護(hù) (Ubiquity Daemon ubd) 體現(xiàn)的 iCloud 架構(gòu)。iCloud 架構(gòu)等待應(yīng)用對(duì)文檔的更改并將其上傳至蘋果云服務(wù)器。同時(shí)也等待從 iCloud 上收到的更改并相應(yīng)修改容器的內(nèi)容。

由于兩個(gè)進(jìn)程完全獨(dú)立于彼此工作,因此需某種形式的仲裁來避免資源競(jìng)爭(zhēng)或丟失容器內(nèi)的文件更新的問題。應(yīng)用需要使用名為 文件協(xié)調(diào) file coordination 的概念來確保對(duì)于每一個(gè)獨(dú)立文件的訪問權(quán)。該訪問權(quán)由 NSFileCoordinator 類提供。概括來說,它為每個(gè)文件提供了一個(gè)簡(jiǎn)單的 讀-寫 鎖。這個(gè)鎖由一個(gè)通知機(jī)制擴(kuò)展,該機(jī)制用于用于改善訪問同一個(gè)文件的不同進(jìn)程間的合作。

這個(gè)通知機(jī)制相比于簡(jiǎn)單的文件鎖來說是有巨大的的好處,并且提供了無縫的用戶體驗(yàn)。iCloud 可能會(huì)在任何時(shí)間把文檔用一個(gè)來自其他設(shè)備的新版本覆蓋。如果一個(gè)應(yīng)用當(dāng)前正在顯示同一個(gè)文檔,它必須從磁盤加載新版本并向用戶展示更新過的內(nèi)容。更新過程中,應(yīng)用可能需要鎖住用戶界面一段時(shí)間并隨后在此打開。甚至可能發(fā)生更壞的情況:應(yīng)用可能保留著未保存的內(nèi)容,這些內(nèi)容需要首先保存到磁盤上以便檢查同步?jīng)_突。最后,在網(wǎng)絡(luò)條件良好的時(shí)候 iCloud 會(huì)上傳文件最近的版本。因此必須能夠要求應(yīng)用立刻保存所有未保存的變更。

為了實(shí)現(xiàn)這個(gè)過程,文件協(xié)調(diào)伴隨著另一套名為 文件展示 (file presentation) 的機(jī)制。無論什么時(shí)候應(yīng)用打開并向用戶展示一個(gè)文件,這被稱為被稱作 展示文檔,并且應(yīng)該注冊(cè)一個(gè)實(shí)現(xiàn)了 NSFilePresenter 協(xié)議的對(duì)象。只要另一個(gè)進(jìn)程通過一個(gè)文件協(xié)調(diào)訪問文件,文件展示者 (file presenter) 就會(huì)收到關(guān)于該文件的通知。這些通知被作為方法調(diào)用傳遞,這些方法在展示者指定的一個(gè)操作隊(duì)列(presentedItemOperationQueue)中異步執(zhí)行。

例如,在任何其他線程被允許開始一個(gè)讀取操作前,文件展示者被要求保存任何未保存的變化。這些操作通過分發(fā)一個(gè) block 到它的展示隊(duì)列來執(zhí)行 savePresentedItemChangesWithCompletionHandler: 方法來完成。展示者需要保存文件并通過執(zhí)行作為參數(shù)傳入的 block 來確認(rèn)通知。除了改變通知,文件展示者還用來通知應(yīng)用同步?jīng)_突。一旦一個(gè)文件的沖突版本被下載,一個(gè)新的文件版本被加入到版本存儲(chǔ)里。所有的展現(xiàn)者通過 presentedItemDidGainVersion: 被通知有一個(gè)新版本被創(chuàng)建。該回調(diào)接收一個(gè)引用了潛在沖突的 NSFileVersion 實(shí)例。

文件展示者還可以被用來監(jiān)視文件夾內(nèi)容。例如,一旦 iCloud 改變文件夾內(nèi)容,如創(chuàng)建,刪除或者移動(dòng)文件,應(yīng)用應(yīng)該被通知到以便更新它的文檔展示。為此,應(yīng)用可以對(duì)展示的目錄注冊(cè)一個(gè)實(shí)現(xiàn)了 NSFilePresenter 協(xié)議的實(shí)例。一個(gè)目錄的文件展示者會(huì)收到任何文件夾或其中文件或子文件夾的改變的通知。比如一個(gè)文件夾內(nèi)的文件被修改,展示者會(huì)收到一個(gè)引用了該文件的 URL 的 presentedSubitemDidChangeAtURL: 通知。

因?yàn)閹捄碗姵貕勖谝苿?dòng)設(shè)備上更加有限,iOS 不會(huì)自動(dòng)從 iCloud 下載新文件。而是由應(yīng)用手動(dòng)決定何時(shí)來觸發(fā)下載新文件到開放性容器中。為了持續(xù)告知應(yīng)用哪些文件可用及其同步狀態(tài),iCloud 還會(huì)同步開放性容器內(nèi)的文件元信息。應(yīng)用可以通過 NSMetadataQuery 或訪問 NSURL 的開放資源屬性查詢這些元信息。無論何時(shí)應(yīng)用想要訪問一個(gè)文件,它一定會(huì)通過 NSFileManagerstartDownloadingUbiquitousItemAtURL:error: 來觸發(fā)下載行為。

深入 iCloud

在繼續(xù)解釋如何實(shí)現(xiàn)文件協(xié)調(diào)和觀察之前,現(xiàn)在我們將深入一些過去幾年里碰到的一些常見問題。再一次的,確保你已經(jīng)閱讀并理解了 Apple iCloud companion guide

雖然這些文件機(jī)制的描述讓它們的使用看起來簡(jiǎn)單明了,但其實(shí)其中有很多隱藏的陷阱。這些陷阱中有些來自于底層框架的 bug。因?yàn)?iCloud 同步延伸到操作系統(tǒng)中相當(dāng)多的層面,人們只能寄希望于蘋果能夠小心的修復(fù)這些 bug。實(shí)際上,蘋果看起來寧愿廢棄壞掉的 API 而不是修復(fù)它們。

即便如此,我們的經(jīng)驗(yàn)告訴我們使用 iCloud 是非常非常容易犯錯(cuò)誤的。異步,協(xié)作,基于鎖特性的文件協(xié)調(diào)和文件展示互相牽連,并不容易掌握。下面,我們將介紹整合 iCloud 文檔同步時(shí)的一些主要規(guī)則,并以這種形式分享我們的經(jīng)驗(yàn)。

只在需要時(shí)使用 Presenters

文件展示者代價(jià)高昂。僅當(dāng)你的應(yīng)用需要立即應(yīng)對(duì)或干預(yù)文件訪問的時(shí)候,才應(yīng)該使用它。

如果你的應(yīng)用正在展示類似文檔編輯器這樣的東西給用戶,文件展示足以勝任。這時(shí),在其他進(jìn)程寫入該文件的時(shí)候也許需要鎖住編輯器,或者還需要保存未保存的改變。然而,如果只是臨時(shí)訪問并且通知也可能會(huì)被延遲處理,就不應(yīng)該使用文件展示。例如,當(dāng)創(chuàng)建文件索引或縮略圖,查看文件更改日期并使用簡(jiǎn)單的文件協(xié)調(diào)可能會(huì)更高效。另外,如果你正展示一個(gè)字典樹的內(nèi)容,在樹的根節(jié)點(diǎn)注冊(cè) 一個(gè) 展示者或用 NSMetadataQuery 來延遲獲取改變通知會(huì)可能會(huì)非常高效。

是什么讓文件展示代價(jià)如此高昂?它需要很多的進(jìn)程間通信:每個(gè)文件上注冊(cè)的展示者在其他進(jìn)程獲取文件的訪問權(quán)時(shí)都被要求釋放該文件。比如另一個(gè)進(jìn)程嘗試讀取一個(gè)文件,該文件的展示者會(huì)被要求保存所有未保存的內(nèi)容 (savePresentedItemChangesWithCompletionHandler:)。它們還會(huì)被要求釋放文件給讀取者(relinquishPresentedItemToReader:),例如文件被讀取時(shí)暫時(shí)鎖住編輯器。

這些通知每一個(gè)都需要分發(fā),加工并由各自的接收者確認(rèn)。并且因?yàn)橹挥袑?shí)現(xiàn)的進(jìn)程知道哪些通知會(huì)被處理,所以即使展示者沒有實(shí)現(xiàn)任何方法,進(jìn)程間也會(huì)為每一個(gè)可能的通知進(jìn)行通信。

另外,每個(gè)步驟都需要在讀取進(jìn)程,展示進(jìn)程和文件協(xié)調(diào)守護(hù)進(jìn)程 (filecoordinationd) 間的多重上下文的切換。結(jié)果就導(dǎo)致了一個(gè)簡(jiǎn)單的文件訪問很快就變成耗費(fèi)資源的操作。

除此之外,如果太多的展示者被注冊(cè),文件協(xié)調(diào)守護(hù)進(jìn)程可能會(huì)刪除重要的系統(tǒng)資源。對(duì)于每一個(gè)展示者,都需要打開并監(jiān)聽每一個(gè)它所描述的路徑上的文件夾。尤其在 OS X Lion 和 iOS 5 上,這些資源是非常稀少的,過度的使用很容易導(dǎo)致文件協(xié)調(diào)守護(hù)進(jìn)程的鎖死或崩潰。

基于這些原因,我們強(qiáng)烈建議不要在目錄樹的每一個(gè)節(jié)點(diǎn)上增加文件展示者,只根據(jù)需要使用最少的文件展示者。

只在需要時(shí)使用協(xié)調(diào)

雖然文件協(xié)調(diào)要比文件展示節(jié)約資源,但它仍然給你的應(yīng)用和整個(gè)系統(tǒng)增加額外的負(fù)擔(dān)。

每當(dāng)你的應(yīng)用正在協(xié)調(diào)一個(gè)文件,其他同時(shí)想要訪問同一個(gè)文件的進(jìn)程可能需要等待。因此你不該在協(xié)調(diào)文件時(shí)執(zhí)行過于耗時(shí)的任務(wù)。如果你這么做了,比如存儲(chǔ)了大文件,你可以考慮將它存儲(chǔ)到一個(gè)臨時(shí)文件夾,隨后在協(xié)調(diào)訪問時(shí)使用硬連接。注意每一個(gè)協(xié)調(diào)的訪問都可能會(huì)觸發(fā)另一個(gè)進(jìn)程上的文件展示者 -- 該展示者可能需要時(shí)間在你的訪問之前更新文件。始終考慮使用諸如 NSFileCoordinatorReadingWithoutChanges 這樣的標(biāo)識(shí),除非需要讀取文件的最新版本。

雖然你的應(yīng)用的開放性容器可能不會(huì)被其他應(yīng)用訪問,過分的文件協(xié)調(diào)仍然可能成為 iCloud 的一個(gè)問題,執(zhí)行太多的協(xié)調(diào)請(qǐng)求會(huì)造成類似 ubd 的進(jìn)程的資源饑餓問題。在應(yīng)用啟動(dòng)階段,ubd 似乎會(huì)掃描開放性容器內(nèi)的所有文件。如果你的應(yīng)用在程序啟動(dòng)階段也在執(zhí)行相同的掃描。兩個(gè)進(jìn)程會(huì)經(jīng)常沖突,從而可能導(dǎo)致協(xié)調(diào)的高開銷。這時(shí)考慮更優(yōu)化的解決方案是明智的。例如掃描目錄內(nèi)容時(shí),單獨(dú)的文件內(nèi)容訪問權(quán)限是根本不需要的。把協(xié)調(diào)工作延遲到文件內(nèi)容真正被展示的時(shí)候再進(jìn)行會(huì)是不錯(cuò)的選擇。

最后,絕對(duì)不要協(xié)調(diào)一個(gè)還沒有被下載的文件。文件協(xié)調(diào)會(huì)觸發(fā)對(duì)該文件的下載。不幸的是,協(xié)調(diào)將會(huì)一直等待直到下載完成,這有可能會(huì)導(dǎo)致應(yīng)用被鎖住很長(zhǎng)一段時(shí)間。訪問一個(gè)文件之前,應(yīng)用應(yīng)該先檢查文件下載狀態(tài)。你可以通過查詢 URL 的 NSURLUbiquitousItemDownloadingStatusKey 的值或使用 NSMetadataQuery 做到這一點(diǎn)。

協(xié)調(diào)方法的幾個(gè)備注

閱讀 NSFileCoordinator 的文檔,你可能注意到每個(gè)方法都有一個(gè)冗長(zhǎng)而復(fù)雜的描述。雖然 API 文檔通常是非??煽康?,但由于同其他協(xié)調(diào)器和文件展示者交互的多樣性,以及文件夾和文件鎖的語法多樣性,都造成了很高的復(fù)雜度。有一些很容易忽略的細(xì)節(jié)和問題貫穿這些長(zhǎng)長(zhǎng)的描述:

  1. 認(rèn)真選擇協(xié)調(diào)選項(xiàng)。它們真的對(duì)文件協(xié)調(diào)器和文件展示者有著影響。比如,如果沒有采用 NSFileCoordinatorWritingForDeleting 標(biāo)識(shí),文件展示者將無法通過 accommodatePresentedItemDeletionWithCompletionHandler: 對(duì)文件刪除操作做出影響。如果移動(dòng)目錄時(shí)不使用 NSFileCoordinatorWritingForMoving,則移動(dòng)操作將不會(huì)等待其子項(xiàng)目上正在執(zhí)行的協(xié)調(diào)操作進(jìn)行完成。
  2. 始終認(rèn)為協(xié)調(diào)調(diào)用可能會(huì)失敗并返回錯(cuò)誤。因?yàn)槲募f(xié)調(diào)同 iCloud 交互,如果被協(xié)調(diào)的文件不能被下載,協(xié)調(diào)調(diào)用會(huì)失敗并產(chǎn)生一條錯(cuò)誤信息,并且你實(shí)際的文件操作可能不會(huì)被執(zhí)行。如果沒有正確的實(shí)現(xiàn)錯(cuò)誤處理方法,你的應(yīng)用可能不會(huì)注意到這樣的問題。
  3. 在進(jìn)入?yún)f(xié)調(diào) block 之后檢查文件狀態(tài)。協(xié)調(diào)請(qǐng)求之后,也許很長(zhǎng)時(shí)間已經(jīng)過去了。這時(shí),應(yīng)用操作文件的前提條件可能已經(jīng)失效。你想寫入的信息直到重新獲得鎖之前有可能都是臟數(shù)據(jù)。也可能在你等待獲得寫入權(quán)限的時(shí)候文件已經(jīng)被刪除。這時(shí)你可能會(huì)無意中再次創(chuàng)建已經(jīng)被刪除的文件。

通知死鎖

實(shí)現(xiàn) NSFilePresenter 的通知處理方法需要特別注意。類似 relinquishPresentedItemToReader: 這樣的通知處理方法必須被確認(rèn)及告知其他進(jìn)程該文件已經(jīng)對(duì)訪問準(zhǔn)備就緒。這一般通過執(zhí)行作為參數(shù)傳入通知處理方法的確認(rèn) block 來完成。確認(rèn) block 被調(diào)用之前,其他進(jìn)程不得不等待,了解這一點(diǎn)是尤為重要的。如果確認(rèn)因?yàn)橥ㄖ幚淼木徛谎舆t,協(xié)調(diào)進(jìn)程也許會(huì)被擱置。如果一直沒有被執(zhí)行,則可能會(huì)永遠(yuǎn)被掛起。

不幸的是,需要被確認(rèn)的通知也會(huì)被其他完全獨(dú)立的通知拖慢。為了確保通知以正確的順序執(zhí)行,presentedItemOperationQueue 一般被設(shè)置為一個(gè)順序執(zhí)行隊(duì)列。但是一個(gè)順序隊(duì)列就意味著處理速度慢的通知會(huì)延緩隨后的通知。尤其是它們會(huì)延緩需要確認(rèn)的通知,在那之前,所有的進(jìn)程都將等待。

例如,假設(shè)一個(gè) presentedItemDidChange 通知首先進(jìn)入隊(duì)列。該回調(diào)漫長(zhǎng)的處理過程將會(huì)延緩其他隨后進(jìn)入隊(duì)列的通知,比如 relinquishPresentedItemToReader:。因此,該通知的確認(rèn)也會(huì)被延遲,從而也導(dǎo)致等待它的進(jìn)程被延緩。

綜上所述,在展示隊(duì)列里的時(shí)候 永遠(yuǎn)不要 執(zhí)行文件協(xié)調(diào)。實(shí)際上,即使簡(jiǎn)單的不需要任何確認(rèn)的通知 (比如 presentedItemDidChange) 也會(huì)導(dǎo)致死鎖。設(shè)想兩個(gè)文件展示者同時(shí)在展示同一個(gè)文件。兩個(gè)展示者都通過執(zhí)行協(xié)調(diào)的讀取操作來處理 presentedItemDidChange 通知。如果文件發(fā)生改變,通知被發(fā)送到兩個(gè)展示者并且二者都在同一個(gè)文件上執(zhí)行協(xié)調(diào)的讀取操作。因此,兩個(gè)展示者都通過入隊(duì)一個(gè) relinquishPresentedItemToReader: 請(qǐng)求對(duì)方釋放文件并等待對(duì)方確認(rèn)。不幸的是,兩個(gè)展示者無法確認(rèn)通知,因?yàn)樗鼈兌家驗(yàn)橛谰玫牡却龑?duì)方確認(rèn)的協(xié)調(diào)請(qǐng)求而阻塞了它們的展示隊(duì)列。我們?cè)?GitHub 上提供了一個(gè)小例子展示這種死鎖。

通知缺陷

從通知中得出正確結(jié)論并不容易。文件展示中存在的 bug 造成了有些通知處理器從未被執(zhí)行。這里初步介紹一些已知的不太規(guī)律的通知:

  1. 除了 presentedSubitemDidChangeAtURL:presentedSubitemAtURL:didMoveToURL:,所有的子項(xiàng)目通知要么不被調(diào)用,要么以一種難以預(yù)測(cè)的方式被調(diào)用。絕對(duì)不要依賴它們 -- 實(shí)際上,presentedSubitemDidAppearAtURL:accommodatePresentedSubitemDeletionAtURL:completionHandler: 從不會(huì)被調(diào)用。
  2. 只有通過使用了 NSFileCoordinatorWritingForDeleting 的文件協(xié)調(diào)來刪除文件,accommodatePresentedItemDeletionWithCompletionHandler: 才會(huì)工作。否則,你會(huì)連一個(gè) change 的通知都收不到。
  3. 只有文件展示者執(zhí)行 itemAtURL:didMoveToURL: 時(shí),presentedItemDidMoveToURL:presentedSubitemAtURL:didMoveToURL: 才會(huì)被調(diào)用。否則項(xiàng)目不會(huì)收到任何有用的通知。子項(xiàng)目仍舊會(huì)分別針對(duì)舊的和新的 URL 收到 presentedSubitemDidChange 通知。
  4. 即使文件被正確移動(dòng),presentedSubitemAtURL:didMoveToURL: 通知也被發(fā)送,你仍然會(huì)針對(duì)舊的和新的 URL 收到兩個(gè)額外的 presentedSubitemDidChangeAtURL: 通知。要做好準(zhǔn)備好處理這個(gè)。

一般來說,你必須注意通知可能會(huì)失效。也不應(yīng)該依賴于任何特定的通知順序。例如,當(dāng)描述一個(gè)目錄樹時(shí),你不能期望父文件夾的通知會(huì)先于或晚于其中子項(xiàng)目的通知。

注意 URL 變化

在文件協(xié)調(diào)和文件展示者傳遞參照著相同文件的不同的 URL 時(shí),有幾種你需要應(yīng)對(duì)的情況。你絕不應(yīng)該使用 isEqual: 比較 URL,因?yàn)閮蓚€(gè)不同的 URL 可能關(guān)聯(lián)同一個(gè)文件。應(yīng)該始終在比較之前標(biāo)準(zhǔn)化它們。這一點(diǎn)在 iOS 上尤為重要,在 iOS 中開放性容器存儲(chǔ)在 /var/mobile/Library/Mobile Documents/ 中,這個(gè)文件夾是 /private/var/mobile/Library/Mobile Documents/ 的符號(hào)鏈接。你會(huì)收到帶有指向同一個(gè)文件,基于 兩種路徑變體 的 URL 的展示者通知。如果你對(duì) iCloud 和本地文檔使用文件協(xié)調(diào)代碼,這個(gè)問題在 OS X 上也會(huì)發(fā)生。

除此之外,還有幾個(gè)關(guān)于大小寫不敏感的文件系統(tǒng)的問題。如果文件系統(tǒng)要求,應(yīng)該始終確保你使用大小寫不敏感的文件名比較。文件協(xié)調(diào) block 和展示者通知可能傳遞使用不同大小寫的相同的 URL 變體。實(shí)際上,這是使用文件協(xié)調(diào)器重命名時(shí)的重要問題。為了搞懂這個(gè)問題,你需要回顧文件實(shí)際上是如何被重命名的:

[coordinator coordinateWritingItemAtURL:sourceURL 
                                options:NSFileCoordinatorWritingForMoving 
                       writingItemAtURL:destURL 
                                options:0 
                                  error:NULL 
                             byAccessor:^(NSURL *oldURL, NSURL *newURL) 
{
    [NSFileManager.defaultManager moveItemAtURL:oldURL toURL:newURL error:NULL];
    [coordinator itemAtURL:oldURL didMoveToURL:newURL];
}];

假設(shè) sourceURL 指向一個(gè)名為 ~/Desktop/my text 的文件,destURL 使用了大寫字母的新文件名 ~/Desktop/My Text。協(xié)調(diào) block 被有意設(shè)計(jì)成傳入兩個(gè) URL 的最新版本,以兼容等待文件訪問時(shí)發(fā)生的移動(dòng)操作。現(xiàn)在,不幸的,當(dāng)改變文件名的大小寫,文件協(xié)調(diào)所執(zhí)行的 URL 校驗(yàn)將會(huì)發(fā)現(xiàn)新舊兩個(gè) URL 都存在一個(gè)有效文件,而新的 URL 是小寫 ~/Desktop/my text 的變體。訪問 block 將會(huì)接收到同樣的 小寫 URL 作為 oldURLnewURL,導(dǎo)致移動(dòng)操作失敗。

請(qǐng)求下載

在 iOS 中,觸發(fā)從 iCloud 的下載是應(yīng)用的責(zé)任??梢酝ㄟ^ NSFileManagerstartDownloadingUbiquitousItemAtURL:error: 方法觸發(fā)下載。如果你的應(yīng)用設(shè)計(jì)成自動(dòng)下載文件 (也就是不由用戶觸發(fā)),你應(yīng)該始終在一個(gè)順序后臺(tái)隊(duì)列中執(zhí)行這些下載請(qǐng)求。換句話說,每一個(gè)單獨(dú)的下載請(qǐng)求涉及到相當(dāng)多的進(jìn)程間通信并可能會(huì)很耗時(shí)。另一方面,同時(shí)觸發(fā)太多的下載有時(shí)會(huì)過載 ubd 守護(hù)進(jìn)程。一個(gè)普遍的錯(cuò)誤就是使用 NSMetadataQuery 等待 iCloud 中的新文件然后自動(dòng)觸發(fā)下載它們。因?yàn)椴樵兘Y(jié)果總是在主隊(duì)列中傳遞并且可能包含一打的更新信息,直接觸發(fā)下載會(huì)阻塞應(yīng)用很長(zhǎng)一段時(shí)間。

為了查詢某個(gè)文件的下載或者上傳狀態(tài),你可以使用 NSURL 的資源值。在 iOS 7 / OS X 10.9 之前,一個(gè)文件的下載狀態(tài)通過 NSURLUbiquitousItemIsDownloadedKey 來確認(rèn)。根據(jù)頭文件文檔,這個(gè)資源值從未正確生效過,所以在 iOS 7 和 Mavericks 中被廢棄了?,F(xiàn)在蘋果建議使用 NSURLUbiquitousItemDownloadingStatusKey。在老系統(tǒng)上,你應(yīng)該使用 NSMetadataQuery 查詢 NSMetadataUbiquitousItemIsDownloadedKey 來獲得正確的下載狀態(tài)。

綜合考慮

為你的應(yīng)用增加 iCloud 支持并不只是你增加的另一個(gè)功能,而是一個(gè)對(duì)應(yīng)用設(shè)計(jì)和實(shí)現(xiàn)有著深遠(yuǎn)影響的決定。它既影響著你的數(shù)據(jù)模型也影響著 UI。所以不要低估支持 iCloud 所需要做出的努力。

最重要的,增加 iCloud 會(huì)引入一個(gè)新的異步層。應(yīng)用必須能夠在任何時(shí)候處理文檔和元數(shù)據(jù)的變化。這些變化上的通知可能會(huì)在不同線程上收到,這就需要在你的整個(gè)應(yīng)用中添加同步機(jī)制來對(duì)這些通知進(jìn)行適當(dāng)?shù)奶幚怼D阈枰⒁饽切?duì)于用戶文檔完整性有重大影響的關(guān)鍵代碼中的問題,比如丟失更新,競(jìng)爭(zhēng)和死鎖等。

始終注意 iCloud 的同步保證是非常脆弱的。你只能假設(shè)文件和包是自動(dòng)同步的。但你不能期望多個(gè)同時(shí)被修改的文件也會(huì)被立刻同步。比如,如果你的應(yīng)用分開存儲(chǔ)元信息和實(shí)際的文件的話,你一定要能夠應(yīng)對(duì)元信息會(huì)先于或晚于實(shí)際文件被下載的情況。

使用 iCloud 文檔同步同時(shí)也意味著你正在做一個(gè)發(fā)布的應(yīng)用。你的文檔會(huì)在運(yùn)行著不同版本的不同設(shè)備上。你可能想要使你文件格式的不同版本向前兼容。起碼,你必須確保你的應(yīng)用在面對(duì)其他不同設(shè)備上安裝的新版本應(yīng)用創(chuàng)建的文件時(shí)不會(huì)崩潰或發(fā)生錯(cuò)誤。用戶未必會(huì)立刻更新所有的設(shè)備,所以預(yù)先準(zhǔn)備好這個(gè)問題。

最后,你的 UI 需要反映同步行為。即使這會(huì)抹殺掉一些神奇之處。尤其在 iOS 上,連接失敗和緩慢的文件轉(zhuǎn)換是現(xiàn)實(shí)狀況。你的用戶應(yīng)該被通知關(guān)于文檔的同步狀態(tài)。你應(yīng)該考慮展示文件是在被上傳還是在下載,以告知用戶他們的文檔現(xiàn)在是否可用。使用大文件時(shí),你可能需要顯示文件傳輸進(jìn)度,你的 UI 應(yīng)該優(yōu)雅一些; 如果 iCloud 不能及時(shí)給你某個(gè)文檔,你的應(yīng)用應(yīng)該響應(yīng),并且讓用戶重試或至少放棄操作。

調(diào)試

因?yàn)樯婕暗蕉嘞到y(tǒng)服務(wù)和外部服務(wù),調(diào)試 iCloud 問題非常困難。Xcode 5 提供的 iCloud 調(diào)試功能非常有限并且大多數(shù)時(shí)候只會(huì)告訴你 iCloud 是否已經(jīng)同步。幸運(yùn)的是,還有一些差不多是官方的方法來調(diào)試 iCloud 文檔存儲(chǔ)。

在 OS X 上調(diào)試

有時(shí)你可能經(jīng)歷過 iCould 停止同步某個(gè)文件或干脆完全停止工作。實(shí)際上,這在文件協(xié)調(diào)器內(nèi)使用斷點(diǎn)或在一個(gè)文件操作進(jìn)行期間殺掉一個(gè)進(jìn)程時(shí)很容易發(fā)生。甚至如果你的應(yīng)用在某個(gè)關(guān)鍵點(diǎn)崩潰后也會(huì)發(fā)生。通常來說,重啟或者注銷后重新登錄 iCloud 都不能修復(fù)這個(gè)問題。

為了修復(fù)這些鎖定,一個(gè)命令行工具會(huì)非常有好處: ubcontrol。這個(gè)工具是 10.7 以后版本 OS X 的一部分。使用命令 ubcontrol -x,你能夠重置文檔同步的本地狀態(tài)。它通過重置一些私有數(shù)據(jù)庫(kù)和緩存,重啟所有涉及到的系統(tǒng)守護(hù)進(jìn)程,來復(fù)原熄火的同步。同時(shí)它也會(huì)存儲(chǔ)一些報(bào)告分析信息到 ~/Library/Application Support/Ubiquity-backups

雖然已經(jīng)有日志文件被寫入 ~/Library/Logs/Ubiquity 中,你也還可以通過 ubcontrol -k 7 來增加日志級(jí)別。在進(jìn)行 iCloud 相關(guān)的錯(cuò)誤報(bào)告時(shí),蘋果工程師經(jīng)常會(huì)要求你這么做以便收集信息。

為了調(diào)試文件協(xié)調(diào),你還可以從文件協(xié)調(diào)守護(hù)進(jìn)程中直接取回鎖狀態(tài)信息。這使你能夠得知在應(yīng)用中或多進(jìn)程間可能遇到的文件協(xié)調(diào)死鎖。為了訪問這個(gè)信息,你需要在終端中執(zhí)行以下命令:

sudo heap filecoordinationd -addresses NSFileAccessArbiter
sudo lldb -n filecoordinationd
po [<address> valueForKey: @"rootNode"]

第一個(gè)命令會(huì)返回一個(gè)文件協(xié)調(diào)守護(hù)進(jìn)程的內(nèi)部單例對(duì)象的地址。隨后,你關(guān)聯(lián) lldb 到運(yùn)行的守護(hù)進(jìn)程上。通過使用第一步取回的地址,你將會(huì)得到一個(gè)所有活動(dòng)的鎖和文件展示者的狀態(tài)的概覽。調(diào)試命令會(huì)展示當(dāng)前正在被展示或協(xié)調(diào)的整個(gè)文件樹。例如,如果 TextEdit 正在展示一個(gè)名為 example.txt 的文件,你會(huì)得到以下跟蹤信息:

example.txt
    <NSFileAccessNode 0x…> parent: 0x…, name: "example.txt"
    presenters:
        <NSFilePresenterProxy …> client: TextEdit …>
        location: 0x7f9f4060b940
    access claims: <none>
    progress subscribers: <none>
    progress publishers: <none>
    children: <none>

如果你在文件協(xié)調(diào)進(jìn)行時(shí)創(chuàng)建這種跟蹤 (比如通過在文件協(xié)調(diào) block 中設(shè)置斷點(diǎn)),你還會(huì)得到一個(gè)等待文件協(xié)調(diào)器的所有進(jìn)程的列表。

如果通過 lldb 觀察文件協(xié)調(diào),你應(yīng)該始終記得盡快執(zhí)行 detach 命令。否則,全局根進(jìn)程文件協(xié)調(diào)守護(hù)進(jìn)程將一直等待,這會(huì)影響到系統(tǒng)中幾乎所有的應(yīng)用。

在 iOS 上調(diào)試

在 iOS 上,調(diào)試要更加復(fù)雜,因?yàn)槟銦o法檢查運(yùn)行的系統(tǒng)進(jìn)程,你也無法使用像 ubcontrol 的命令行工具。

iCloud 鎖定在 iOS 上似乎更經(jīng)常發(fā)生。重啟應(yīng)用或設(shè)備都無效。唯一有效的修復(fù)這種問題的方法是 冷啟動(dòng)。在冷啟動(dòng)過程中,iOS 似乎進(jìn)行了 iClouds 的內(nèi)部數(shù)據(jù)庫(kù)重置??梢酝ㄟ^同時(shí)按下電源鍵和 home 鍵 10 秒鐘冷啟動(dòng)設(shè)備。

為了在 iOS 上激活更詳細(xì)的日志,在蘋果 developer downloads page 有一個(gè)專用的 iCloud 日志概述。如果搜索 "Bug Reporter Logging Profiles (iOS)",你將會(huì)找到一個(gè)叫做 "iCloud Logging Profile" 移動(dòng)設(shè)備概述。在你的 iOS 設(shè)備上安裝該文件來激活更詳細(xì)的日志。你可以用 iTunes 同步設(shè)備來訪問這些日志.隨后,你可以在 Library/Logs/CrashReporter/Mobile Device/<Device Name>/DiagnosticLogs/Ubiquity 文件夾找到它。如果想要關(guān)掉這種加強(qiáng)的日志輸出,從設(shè)備刪除描述文件即可。蘋果建議你在激活或關(guān)閉概述前重啟設(shè)備。

在 iCloud Servers 上調(diào)試

除了在你自己的設(shè)備上調(diào)試,考慮使用蘋果服務(wù)上的調(diào)試服務(wù)可能也會(huì)有用。developer.icloud.com 上有一個(gè)特殊的 web 應(yīng)用,它允許你瀏覽存儲(chǔ)在開放性容器內(nèi)的所有信息和當(dāng)前傳輸狀態(tài)。

過去的幾個(gè)月,蘋果還提供了安全地在服務(wù)端對(duì)所有已連接設(shè)備進(jìn)行 iCloud 重置的方法。更多信息可查看 support document。