iOS 利用 iCloud Document 同步本地文件(swift 3版本)

要用iCloud來實現(xiàn)本地文件與云端文件的同步溜歪,首先要處理好邏輯的問題漂佩。

什么時候上傳庄岖?什么時候下載豁翎?先上傳還是先下載?如何處理文件沖突隅忿?

我要實現(xiàn)的是每天保存一張圖片文件(當天過后就不能再修改這個圖片文件)心剥,用來記錄自己的生活狀態(tài),所以文件系統(tǒng)比較簡單背桐,我希望我的app邏輯也盡量簡單优烧,這樣比較好維護。所以我設(shè)想了一個最簡單的同步邏輯:

點擊同步按鈕后牢撼,先上傳本地 所有的文件匙隔,如果發(fā)現(xiàn)要上傳的文件在云端已經(jīng)有了同名文件,那么就不上傳這一張圖片文件。然后再從云端下載所有文件到本地纷责,同樣捍掺,檢查本地是否有同名文件,如果有再膳,則不下載這張圖片文件挺勿。最后,每次創(chuàng)建了一張圖片文件時喂柒,就上傳到云端不瓶,執(zhí)行覆蓋操作,也就是有同名文件的話灾杰,就把云端的同名文件覆蓋蚊丐,以本地上傳的為主。

![](http://upload-images.jianshu.io/upload_images/530099-9c100d25716998d9.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

這個邏輯我覺得簡單粗暴艳吠,可行麦备。那么就開始吧。

開啟iCloud Document 能力

在Targets ->Capabilities->iCloud 中打開iCloud特性昭娩,如下圖:

UIDocument

我們使用UIDocument這個類來處理文件的上傳和下載的凛篙,不能直接使用這個類,而要創(chuàng)建它的子類栏渺,并重新實現(xiàn)下面兩個方法來處理數(shù)據(jù):

import UIKit

class MyDocument: UIDocument {
    var data:NSData?
    var imgData:NSData?
    
   //處理文件上傳
    override func contents(forType typeName: String) throws -> Any {
        if typeName == "public.png" {
            return imgData ?? NSData()
        } else {
            return Data()
        }
    }
    
    //處理文件下載
    override func load(fromContents contents: Any, ofType typeName: String?) throws {
        if let userContent = contents as? NSData {
            data = userContent
        }
    }

}

當我在其它地方使用這個類的實例對象document執(zhí)行save操作的時候呛梆,就會走 contents(forType typeName: String) 方法來把這個方法里面返回值上傳到iCloud,對于我們也就是imgData磕诊;所以我們在執(zhí)行save操作前填物,要給MyDocument里面的imgdata賦值。

document.save(to: ubiquityURL2,
        for: .forOverwriting,
        completionHandler: {(success: Bool) -> Void in
            if success {
                print("iCloud create OK \(ubiquityURL2)")
            } else {
              print("iCloud create failed \(ubiquityURL2)")
            }
})
//在執(zhí)行save操作前秀仲,要給MyDocument里面的imgdata賦值融痛。
let img = UIImage.init(contentsOfFile: fullpath);
if (img != nil) {
    let imgData = NSData.init(contentsOfFile: fullpath)
    document.imgData = imgData
 }

而document在執(zhí)行open操作的時候,也就是保存文件到本地神僵,這個時候會走 load(fromContents contents: Any, ofType typeName: String?)方法雁刷,這個方法里面的參數(shù)contents就是iCloud傳給我們的數(shù)據(jù),我們可以對這個數(shù)據(jù)進行處理保礼。在這里我使用一個data接收了這個數(shù)據(jù)沛励,然后在document.open的success的block里面進行處理,把它保存到了本地炮障。

document.open(completionHandler: {(success: Bool) -> Void in
      if success {
          let data = document.data
           if data == nil {
              print("iCloud file open failed,error ,data = nil ")
          }else {
              Utils.saveFileToImgFolder(fileName: fileName!, data: data!)
              print("iCloud file open OK")
           }
          
      } else {
          print("iCloud file open failed")
      }
})

ubiquityURL

你肯定已經(jīng)注意到了目派,在上面我說的document.save方法里面,有一個參數(shù)ubiquityURL2胁赢,它前面有一個to企蹭,這個就是ubiquityURL,它說明了了一個文件在iCloud中保存的位置信息。

舉個例子:bundle id如果是com.test.demo,那么對應(yīng)的ubiquityURL是類似這種:file:///private/var/mobile/Library/Mobile%20Documents/iCloudcomtest~demo.我們需要在后面拼接上Documents來表示這個文件是保存在iCloud中的我的這個應(yīng)用的Documents下谅摄。也就是說iCloudcomtest~demo是用來區(qū)分是哪個應(yīng)用的徒河,iCloudcomtest~demo后面的是用來區(qū)分應(yīng)用下文件路徑的。

獲取你的應(yīng)用的跟ubiquityURL的方法是通過以下方法:

    func initUbiquityURL() {
        let filemgr = FileManager.default
        let ubiquityURL = filemgr.url(forUbiquityContainerIdentifier: nil)
        //如果ubiquityURL = nil送漠,可能是沒有登錄賬戶顽照,或者沒有打開iCloud
        guard ubiquityURL != nil else {
            print("Unable to access iCloud Account")
            print("Open the Settings app and enter your Apple ID into iCloud settings")
            return
        }
    }

需要說明的是,MyDocument的初始化是需要ubiquityURL的闽寡,準確來說是它處理的這個文件的ubiquityURL代兵。可以這么理解爷狈,UIDocument的每個實例都是處理一個文件植影,不同的文件處理會創(chuàng)建不同的實例。

let document = MyDocument(fileURL: ubiquityURL2)

例如:這里的ubiquityURL2=file:///private/var/mobile/Library/Mobile%20Documents/iCloudcomtest~demo/Documents/2017-03-02.png

查詢云端文件

我們需要獲取到云端的文件淆院,其實也是需要知道云端文件對應(yīng)的ubiquityURL何乎,而本地的文件也可以轉(zhuǎn)成對應(yīng)的ubiquityURL(這個后面說),所以這樣土辩,云端和本地就構(gòu)成了一種對應(yīng)的關(guān)系。

查詢云端文件抢野,可以主動調(diào)用下面的方法:

    //查詢云端的文件
    func askForCloudDataQuery() {
        metaDataQuery = NSMetadataQuery()
        metaDataQuery?.searchScopes =
            [NSMetadataQueryUbiquitousDocumentsScope]
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(
                                                CloudDataManager.metadataQueryDidFinishGathering),
                                               name: NSNotification.Name.NSMetadataQueryDidFinishGathering,
                                               object: metaDataQuery!)
        metaDataQuery!.start()
    }

查詢到結(jié)果拷淘,可以走下面的回調(diào)方法。

考慮到不需要查詢那么多次指孤,我直接查詢到Document下所有的文件启涯,然后根據(jù)文件名與本地文件名作對比。比對出兩個包含ubiquityURL元素的數(shù)組恃轩,一個是需要上傳到云端的结洼,一個是需要下載到本地的,然后分別進行比對操作叉跛。

//獲取到云端的所有的文件的url松忍,保存到cloudURLs數(shù)組里面
    func metadataQueryDidFinishGathering(notification: NSNotification) -> Void
    {
        let query: NSMetadataQuery = notification.object as! NSMetadataQuery
        query.disableUpdates()
        NotificationCenter.default.removeObserver(self,
                                                  name: NSNotification.Name.NSMetadataQueryDidFinishGathering,
                                                  object: query)
        query.stop()
        print("query.valueLists = \(query.valueLists),query.resultCount = \(query.resultCount)")
        if cloudURLs == nil {
            cloudURLs = Array.init()
        }else {
            cloudURLs?.removeAll()
        }
        if query.resultCount != 0 {
            let count = (query.results as Array).count

            for i in 0..<count{
                let resultURL = query.value(ofAttribute: NSMetadataItemURLKey,
                                            forResultAt: i) as! URL
             
                cloudURLs?.append(resultURL)
            }
            print("cloudURLs = \(cloudURLs!)")
            //下載到本地
            self.downloadFileToLocal()

        }
        //上傳到云端
        self.uploadFileToCloud()
        
    }
//獲取要上傳到icloud的url的數(shù)組
    func getNeedUploadToCloudUbiquityURLArray() -> [URL] {
        var finalArr = localURLs
        for local in localURLs! {
            for cloud in cloudURLs! {
                if cloud == local {
                    let index = finalArr?.index(of: local)
                    finalArr?.remove(at: index!)
                    continue
                }
            }
        }
        print("getNeedUploadToCloudUbiquityURLArray finalArr = \(finalArr!)")
        return finalArr!
    }

獲得本地文件

//獲得本地所有文件的需要上傳到cloud的時候的url,保存到localURLs數(shù)組里面
    func initLocalURLs() {
        if localURLs == nil {
            localURLs = Array.init()
        }else {
            localURLs?.removeAll()
        }
        let allImgNames = Utils.getAllFileNameInImgFolder()
        for imgName in allImgNames {
            let imgURL = ubiquityURLOfImg?.appendingPathComponent(imgName)
            localURLs?.append(imgURL!)
        }
        print("localURLs = \(localURLs)")

    }
    
    
    //獲取要保存到本地的url的數(shù)組
    func getNeedDownloadTolocalUbiquityURLArray() -> [URL] {
        var finalArr = cloudURLs
        for cloud  in cloudURLs! {
            for local in  localURLs! {
                if cloud == local {
                    let index = finalArr?.index(of: cloud)
                    finalArr?.remove(at: index!)
                    continue
                }
            }
        }
        print("getNeedDownloadTolocalUbiquityURLArray finalArr = \(finalArr!)")
        return finalArr!
    }

上傳下載操作

獲取到要上傳和下載的數(shù)組后筷厘,進行上傳和下載的操作就可以了鸣峭。

//保存文件到本地
    func downloadFileToLocal()  {
        //保存文件到本地
        let downloadArr = cloudURLs!// self.getNeedDownloadTolocalUbiquityURLArray()
        for ubiquityURL in downloadArr {
            let fileName = ubiquityURL.path.components(separatedBy: "/").last
            let document = MyDocument(fileURL: ubiquityURL as URL)
            document.open(completionHandler: {(success: Bool) -> Void in
                if success {
                    let data = document.data
                    if data == nil {
                        print("iCloud file open failed,error ,data = nil ")

                    }else {
                        Utils.saveFileToImgFolder(fileName: fileName!, data: data!)
                        print("iCloud file open OK")
                    }
                    
                } else {
                    print("iCloud file open failed")
                }
            })
        }
    }
//上傳文件到云端
    func uploadFileToCloud()  {
        //上傳文件到icloud
        let uploadArr = self.getNeedUploadToCloudUbiquityURLArray()
        for ubiquityURL2 in uploadArr {
            let document = MyDocument(fileURL: ubiquityURL2)
            let fileName = ubiquityURL2.path.components(separatedBy: "/").last

            let fileManager = FileManager.default
            let fullpath = Utils.getImgFolderPath().appending("/\(fileName!)")
            if fileManager.fileExists(atPath: fullpath) {
//                print("FILE AVAILABLE")
            } else {
                print("FILE NOT AVAILABLE at \(fullpath)")
            }
            let img = UIImage.init(contentsOfFile: fullpath);
            if (img != nil) {
                let imgData = NSData.init(contentsOfFile: fullpath)
                document.imgData = imgData
            }
            document.save(to: ubiquityURL2,
                           for: .forOverwriting,
                           completionHandler: {(success: Bool) -> Void in
                            if success {
                                print("iCloud create OK \(ubiquityURL2)")
                            } else {
                                print("iCloud create failed \(ubiquityURL2)")
                            }
            })
        }
    }

做完這些,基本就大功告成了酥艳,一個簡單的iCloud同步功能就實現(xiàn)了摊溶。

需要注意一點的是,在同步之前充石,一定要判斷iCloud是否可用莫换,只有可用的情況下,才能進行上傳和下載,不然就會導致crash拉岁。

判斷是否可用坷剧,只需要判斷ubiquityURL是否為nil就可以,如果為nil膛薛,說明iCloud 賬號沒有再手機上登錄听隐,或者是iCloud Drive按鈕沒有打開。

源代碼:

最后哄啄,代碼可以在這里找到雅任。僅供參考。使用語言為swift 3 咨跌。
https://github.com/chenhuaizhe/src/tree/master/swift/iCloudDemoCode

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沪么,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子锌半,更是在濱河造成了極大的恐慌禽车,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刊殉,死亡現(xiàn)場離奇詭異殉摔,居然都是意外死亡,警方通過查閱死者的電腦和手機记焊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門逸月,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人遍膜,你說我怎么就攤上這事碗硬。” “怎么了瓢颅?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵恩尾,是天一觀的道長。 經(jīng)常有香客問我挽懦,道長翰意,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任巾兆,我火速辦了婚禮猎物,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘角塑。我一直安慰自己蔫磨,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布圃伶。 她就那樣靜靜地躺著堤如,像睡著了一般蒲列。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搀罢,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天蝗岖,我揣著相機與錄音,去河邊找鬼榔至。 笑死抵赢,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的唧取。 我是一名探鬼主播铅鲤,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枫弟!你這毒婦竟也來了邢享?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤淡诗,失蹤者是張志新(化名)和其女友劉穎骇塘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體韩容,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡款违,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了群凶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奠货。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖座掘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柔滔,我是刑警寧澤溢陪,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站睛廊,受9級特大地震影響形真,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜超全,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一咆霜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘶朱,春花似錦蛾坯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽救军。三九已至,卻和暖如春倘零,著一層夾襖步出監(jiān)牢的瞬間唱遭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工呈驶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拷泽,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓袖瞻,卻偏偏與公主長得像司致,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子虏辫,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蚌吸,服務(wù)發(fā)現(xiàn),斷路器砌庄,智...
    卡卡羅2017閱讀 134,707評論 18 139
  • 通過iOS 8app extensions,我們可以選擇多種方式去分享我們app的功能羹唠。Document Prov...
    _淺墨_閱讀 7,203評論 4 12
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件娄昆、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,121評論 4 61
  • 記得三年前剛步入大學校園的時候萌焰,我是多么的激動哺眯,興奮!當時就想著扒俯,為自己的大學生活好好的規(guī)劃一下奶卓,于是就做了...
    一夢遠方閱讀 1,469評論 4 8
  • 文丨pinkpink-summer 小丹同學翹著二郎腿悠閑的躺在床上刷手機夺姑,忽然她轉(zhuǎn)過頭來語氣凝重的問我,舅媽掌猛,你...
    pinkpinksummer閱讀 234評論 0 0