iOS實(shí)現(xiàn)iCloud數(shù)據(jù)存儲(chǔ)-Swift3.0

前言:作為一名swift初學(xué)者,我希望能通過寫文章的方式來更好地幫助自己學(xué)習(xí),更希望能得到你的建議和批評(píng)。
     如有紕漏之處,還望不吝賜教。

一.簡(jiǎn)介

在使用一些不需要服務(wù)端支持的App時(shí),我發(fā)現(xiàn)部分App通過使用iCloud文檔存儲(chǔ)功能來滿足應(yīng)用數(shù)據(jù)云存儲(chǔ)的需求,如饑荒的游戲數(shù)據(jù)存儲(chǔ)、素記的日記存儲(chǔ)等,用戶可以在自己iCloud賬號(hào)下的任何設(shè)備訪問或修改App的這部分?jǐn)?shù)據(jù),十分方便。

二.基本概念

在iOS iCloud存儲(chǔ)中,蘋果提供了三個(gè)功能,分別是:

  • Key-value storage
  • iCloud Documents
  • CloudKit
    1.Key-value storage
    顧名思義,這是一個(gè)類似于iOS里NSUserDefaults的通過鍵值對(duì)來保存簡(jiǎn)單數(shù)據(jù)的功能,這種Property-list數(shù)據(jù)格式適合存儲(chǔ)一些非關(guān)鍵數(shù)據(jù),如用戶配置。
    2.iCloud Documents
    這個(gè)功能提供了文件及目錄的數(shù)據(jù)類型,這個(gè)特性決定了此功能相較于Key-value storage更適合進(jìn)行關(guān)鍵數(shù)據(jù)的存儲(chǔ)。
    3.CloudKit
    相較于前兩個(gè),CloudKit就要復(fù)雜得多,這是蘋果為開發(fā)者提供的一整套數(shù)據(jù)庫工具,類似于Maxleap這類第三方云服務(wù)。開發(fā)者通過蘋果提供的Cloud dashboard網(wǎng)站可以配置所需的表結(jié)構(gòu),并通過在代碼中導(dǎo)入CloudKit進(jìn)行數(shù)據(jù)庫操作。

可以看出,這三個(gè)數(shù)據(jù)庫工具是蘋果為了滿足不同層次的數(shù)據(jù)云存儲(chǔ)場(chǎng)景而設(shè)置的,所以在使用前要根據(jù)自己的需要來選擇相對(duì)應(yīng)的工具。對(duì)我而言,iCloud Documents更能滿足我的需求,下面將開始iCloud Documents的學(xué)習(xí)。

三.iCloud Documents的深入學(xué)習(xí)

簡(jiǎn)單說來,iCloud Documents只需要兩個(gè)類就可以實(shí)現(xiàn)其功能:
1.類名:NSMetadataQuery
功能:定位數(shù)據(jù),也就是查詢文件列表。
2.類名:UIDocument
功能:文件操作,對(duì)某一個(gè)文件進(jìn)行操作,包括新增文件。

四.iCloud Documents功能的簡(jiǎn)單實(shí)現(xiàn)

1.準(zhǔn)備工作

1.1新建項(xiàng)目

1.2開啟iCloud功能

如圖:


7DC897D8-4B53-49DC-97F4-F36BB26DCFE8.png

這里需要注意的是如果在Capabilities里沒有找到這個(gè)選項(xiàng)的話,你需要的是一個(gè)付費(fèi)的蘋果開發(fā)者賬號(hào)。
而配置里的Containers就像iOS的沙盒目錄一樣,將app內(nèi)不同的文件目錄分開,如果你需要多個(gè)容器的話請(qǐng)自行配置,這里我選擇默認(rèn)容器。

1.3在你的iOS設(shè)備上登錄iCloud賬號(hào)

1.4創(chuàng)建一個(gè)簡(jiǎn)單的列表視圖,增加輸入、保存、修改、刪除這四個(gè)按鈕

2.檢測(cè) iCloud 可用性

使用 iCloud Documents 之前,需要檢測(cè)當(dāng)前設(shè)備是否開啟了 iCloud 功能。
這里通過獲取iCloudDocuments路徑來進(jìn)行可用性判斷:

func iCloudDocumentURL() -> URL? {
    let fileManager = FileManager.default
    if let url = fileManager.url(forUbiquityContainerIdentifier: nil) {
      return url.appendingPathComponent("Documents")
    }
    return nil
}

3.查詢文件列表

確認(rèn)iCloud可用之后,我們查詢Documents目錄下的子目錄。這一步,通過NSMetadataQuery這個(gè)類來實(shí)現(xiàn):

    private var query : NSMetadataQuery = NSMetadataQuery()
    func loadDocuments()->Bool{
        let baseURL = self.iCloudDocumentURL()
        guard baseURL != nil else {
            return false
        }
        let center = NotificationCenter.default
        query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
        query.predicate = NSPredicate(value: true)
        center.addObserver(self, selector: #selector(metadataQueryDidFinishGathering), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: nil)
        self.query.enableUpdates()
        query.start()
        return true
    }

searchScopes這個(gè)屬性用來設(shè)置所需查詢的目錄,NSMetadataQueryUbiquitousDocumentsScope為子目錄,也就是Documents下的目錄或文件。predicate可根據(jù)自己的需求設(shè)置查詢條件。
查詢的結(jié)果通過在通知中心注冊(cè)觀察者來監(jiān)聽,注冊(cè)后就可以開始查詢。
監(jiān)聽到查詢結(jié)束的通知后,建議關(guān)閉查詢操作。
這樣,我們就得到了Documents目錄下的所有子文件,子文件以NSMetadataItem的實(shí)例存在query.results數(shù)組里。接下來,通過對(duì)NSMetadataItem實(shí)例調(diào)用value(forAttribute: NSMetadataItemURLKey)這樣的方法來獲取子文件的URL、文件名、修改時(shí)間等信息。代碼如下:

 func metadataQueryDidFinishGathering() {
        query.disableUpdates()
        query.stop()
        let center = NotificationCenter.default
        center.removeObserver(self)
        var diaryList = Array<Any>()
        if (query.resultCount == 1) {
            let item = query.results.first as! NSMetadataItem
            let fileURL = item.value(forAttribute: NSMetadataItemURLKey) as! URL
            let document = XDocument(fileURL: fileURL )
            document.open(completionHandler: { (success) in
                for dic in document.diaries{
                    let diary = Diary(dic: dic)
                    diaryList.append(diary)
                }
                self.delegate?.queryDocumentsComplete(results: diaryList)
                document.close(completionHandler: nil)
            })

        }else{
            self.delegate?.queryDocumentsComplete(results: diaryList)
        }
    }

示例代碼中僅在Documents目錄下保存了一個(gè)文件,所以處理查詢結(jié)果時(shí)也只是操作這一個(gè)文件,你可以根據(jù)自己的需求對(duì)results遍歷及操作。在上述代碼中可以看到,獲取到子文件的路徑后,我是通過繼承自UIDocument的XDocument這個(gè)類進(jìn)行文件讀取的,具體原因在下面詳述。

4.實(shí)現(xiàn)UIDocument的方法進(jìn)行文件操作

iCloud 的官方文檔中強(qiáng)制要求使用者對(duì)文件的操作通過 NSFileCoordinator 和 NSFilePresenter 來進(jìn)行的,而UIDocument就是對(duì)這兩個(gè)類的封裝,由于iCloud的文件可以在用戶的不同設(shè)備進(jìn)行操作,所以為了讀寫安全,我們需要使用UIDocument對(duì)文件進(jìn)行操作。
需要注意的是,UIDocument不能直接使用,我們需要自己實(shí)現(xiàn)對(duì)文件內(nèi)容的操作邏輯,即繼承UIDocument并實(shí)現(xiàn)contents(forType typeName: String)和load(fromContents contents: Any, ofType typeName: String?) 這兩個(gè)方法。這兩個(gè)回調(diào)方法前者是用來自定義所需保存的數(shù)據(jù),后者是用來在我們將獲取的數(shù)據(jù)解析后保存起來。代碼如下:

let kArchiveKey  = "Diary"
import UIKit
class XDocument: UIDocument {
    var diaries : Array<Dictionary<String, Any>>!
    override func contents(forType typeName: String) throws -> Any {
        let data = NSMutableData.init()
        let archiver:NSKeyedArchiver = NSKeyedArchiver.init(forWritingWith: data)
        archiver.encode(self.diaries, forKey: kArchiveKey)
        archiver.finishEncoding()
        return data
    }
    override func load(fromContents contents: Any, ofType typeName: String?) throws {
        let unarchiver:NSKeyedUnarchiver = NSKeyedUnarchiver.init(forReadingWith: contents as! Data)
        self.diaries = unarchiver.decodeObject(forKey: kArchiveKey) as! Array
        unarchiver.finishDecoding()
    }
}

代碼中,我將Diary這個(gè)類轉(zhuǎn)成Dictionary后所保存在的數(shù)據(jù)列表用NSKeyedArchiver轉(zhuǎn)成Data保存起來。
這樣我們可以使用XDocument這個(gè)類對(duì)我們的數(shù)據(jù)進(jìn)行保存及修改,記得在讀寫操作前分別調(diào)用UIDocument的open及close方法。
新建文件或者修改文件都是通過save(to url: URL, for saveOperation: UIDocumentSaveOperation, completionHandler: ((Bool) -> Swift.Void)? = nil)這個(gè)方法進(jìn)行的,區(qū)別在于UIDocumentSaveOperation這個(gè)枚舉,


屏幕快照 2017-01-03 下午6.12.33.png

一目了然。
具體代碼如下:

func save(diaries:Array<Diary>){
    let baseURL = self.iCloudDocumentURL()
    if baseURL != nil{
        var dataList = Array<Dictionary<String, Any>>()
        for diary in diaries {
            let dic = diary.convertToDictionary()
            dataList.append(dic)
        }
        var url = self.iCloudDocumentURL()
        url = url?.appendingPathComponent("saveData")
        let document = XDocument(fileURL: url!)
        document.open { (success) in
            if(success){
                document.diaries = dataList
                document.save(to: url!, for: .forOverwriting) { (success) in
                    if success{
                        print("Overwrit success")
                        document.close(completionHandler: nil)
                    }
                }
            }else{
                document.diaries = dataList
                document.save(to: url!, for: .forCreating) { (success) in
                    if success{
                        print("Creat success")
                        document.close(completionHandler: nil
                   }
                }
            }
        }
    }
}

這樣,我們就簡(jiǎn)單的實(shí)現(xiàn)了iCloud documents的數(shù)據(jù)存儲(chǔ)。
如果demo中有任何問題或疑問,請(qǐng)告訴我,謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容