icloud UIDocument 文件同步學(xué)習(xí)小結(jié)

UIDocument

  1. 如何使用icloud 同步文件
  2. 如何添加文件到icloud
  3. 如何獲取icloud中文件列表
  4. 如何刪除icloud中文件
  5. 多個(gè)設(shè)備同時(shí)打開(kāi)一個(gè)document 如何同步
  6. 多個(gè)設(shè)備如何同步document列表
  7. 如何處理沖突

1. 基本配置

首先添加icloud capability,這個(gè)需要開(kāi)發(fā)者賬號(hào), 然后添加一個(gè)icloud container id熄攘。

2. 添加數(shù)據(jù)

想把一個(gè)文件數(shù)據(jù)放到icloud上录语,要先檢查icloud 是否可用

  1. 檢查icloud是否已經(jīng)可用,并獲取到icloud container url
  guard FileManager.default.ubiquityIdentityToken != nil else {
            print("?? iCloud isn't enabled yet. Please enable iCloud and run again.")
            return nil
        }
        
        // Dispatch to a global queue because url(forUbiquityContainerIdentifier:) might take a nontrivial
        // amount of time to set up iCloud and return the requested URL
        //
        DispatchQueue.global().async {
            if let url = FileManager.default.url(forUbiquityContainerIdentifier: containerIdentifier) {
                DispatchQueue.main.async {
                    self.containerRootURL = url
                }
                return
            }
            print("?? Failed to retrieve iCloud container URL for:\(containerIdentifier ?? "nil")\n"
                    + "Make sure your iCloud is available and run again.")
        }

上面代碼檢查icloud 功能是否開(kāi)啟,并獲取icloud container 容器的url 地址芯侥。

  1. 下面要封裝數(shù)據(jù)缠犀,然后放到icloud container里面
    需要自定義類(lèi)繼承UIDocument,然后通過(guò)操作document來(lái)實(shí)現(xiàn)文件的增刪改
   let document = Document(fileURL: fileURL)
        document.save(to: fileURL, for: .forCreating) { _ in
            document.close { success in
                if !success {
                    print("Failed to close the document: \(fileURL)")
                }
                completionHandler?(success)
            }
        }


  func removeDocument(at fileURL: URL) {
        DispatchQueue.global().async {
            NSFileCoordinator().coordinate(writingItemAt: fileURL, options: .forDeleting, error: nil) { newURL in
                do {
                    try FileManager.default.removeItem(atPath: newURL.path)
                } catch let error as NSError {
                    print(error.localizedDescription)
                }
            }
        }
    }

要看看文件是否已經(jīng)添加成功臭觉,可以在設(shè)置->iCloud->存儲(chǔ)空間昆雀,
但是要配置一些數(shù)據(jù)

 <key>NSUbiquitousContainers</key>
    <dict>
        <key>iCloud.com.example.apple-samplecode.SimpleiCloudDocument</key>
        <dict>
            <key>NSUbiquitousContainerIsDocumentScopePublic</key> // 是否支持在File app中查看
            <true/>
            <key>NSUbiquitousContainerName</key> // 在File app中顯示的名稱(chēng)
            <string>SimpleiCloudDocument</string>
            <key>NSUbiquitousContainerSupportedFolderLevels</key>
            <string>ANY</string>
        </dict>
    </dict>

要實(shí)現(xiàn)在File app查看自己app中數(shù)據(jù),并實(shí)現(xiàn)點(diǎn)擊自定義格式文件能打開(kāi)app蝠筑,要實(shí)現(xiàn)配置CFBundleDocumentTypes and UTExportedTypeDeclarations
設(shè)置LSSupportsOpeningDocumentsInPlace = true
實(shí)現(xiàn)scene(_:openURLContexts:) 方法 具體配置看demo

<key>UTExportedTypeDeclarations</key>
    <array>
        <dict>
            <key>UTTypeConformsTo</key>
            <array>
                <string>com.apple.package</string>
                <string>public.content</string>
                <string>public.data</string>
            </array>
            <key>UTTypeDescription</key>
            <string>AIScanner</string>
            <key>UTTypeIconFiles</key>
            <string>Icon.png</string>
            <key>UTTypeIdentifier</key>
            <string>com.daping.pdfdoc.icloud.aisc</string>
            <key>UTTypeTagSpecification</key>
            <dict>
                <key>public.filename-extension</key>
                <array>
                    <string>aisc</string>
                </array>
                <key>public.mime-type</key>
                <string>application/aisc</string>
            </dict>
        </dict>
    </array>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeIconFiles</key>
            <array/>
            <key>CFBundleTypeName</key>
            <string>AIScanner</string>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>LSHandlerRank</key>
            <string>Owner</string>
            <key>LSTypeIsPackage</key>
            <true/>
            <key>LSItemContentTypes</key>
            <array>
                <string>com.daping.pdfdoc.icloud.aisc</string>
            </array>
        </dict>
    </array>
    <key>NSUbiquitousContainers</key>
    <dict>
        <key>iCloud.com.danping.pdfdoc.aiscanner</key>
        <dict>
            <key>NSUbiquitousContainerIsDocumentScopePublic</key>
            <true/>
            <key>NSUbiquitousContainerName</key>
            <string>SimpleiCloudDocument</string>
            <key>NSUbiquitousContainerSupportedFolderLevels</key>
            <string>ANY</string>
        </dict>
    </dict>
    <key>LSSupportsOpeningDocumentsInPlace</key>
    <true/>

3. 查詢(xún)數(shù)據(jù)

如何查詢(xún)icloud document
當(dāng)在一個(gè)設(shè)備添加了document狞膘, icloud并不會(huì)立即把數(shù)據(jù)同步到其他設(shè)備,而是先同步metadata什乙,因?yàn)閙etadata 數(shù)據(jù)量小挽封,根據(jù)不同設(shè)置選擇是否立即同步數(shù)據(jù),iOS設(shè)備不會(huì)立即同步臣镣,mac會(huì)立即同步辅愿。
其他設(shè)置收到icloud 新數(shù)據(jù)通知后,數(shù)據(jù)并沒(méi)有同步過(guò)來(lái)忆某,所有不能使用FileManager API 去操作數(shù)據(jù)渠缕。
要查詢(xún)和監(jiān)聽(tīng)icloud 數(shù)據(jù)變換要使用 NSMetadataQuery 接口。

metadataQuery.notificationBatchingInterval = 1
metadataQuery.searchScopes = [NSMetadataQueryUbiquitousDataScope, NSMetadataQueryUbiquitousDocumentsScope]
metadataQuery.predicate = NSPredicate(format: "%K LIKE %@", NSMetadataItemFSNameKey, "*." + Document.extensionName)
metadataQuery.sortDescriptors = [NSSortDescriptor(key: NSMetadataItemFSNameKey, ascending: true)]
metadataQuery.start()

當(dāng)讀取數(shù)據(jù)的時(shí)候褒繁,為了避免沖突亦鳞,先關(guān)閉查詢(xún)接口,然后再開(kāi)啟。

func metadataItemList() -> [MetadataItem] {
    var result = [MetadataItem]()
    metadataQuery.disableUpdates()
    if let metadatItems = metadataQuery.results as? [NSMetadataItem] {
        result = metadataItemList(from: metadatItems)
    }
    metadataQuery.enableUpdates()
    return result
}

4. 沖突解決

icloud 多個(gè)設(shè)備之前同步數(shù)據(jù)燕差,不同的網(wǎng)絡(luò)環(huán)境遭笋,沖突不可避免。比如多個(gè)設(shè)備都斷網(wǎng)徒探,然后同時(shí)修改數(shù)據(jù)瓦呼,再同時(shí)打開(kāi)網(wǎng)絡(luò), 就會(huì)出現(xiàn)沖突测暗。

  @objc
    func documentStateChanged(_ notification: Notification) {
        guard let document = document else { return }
        printDocumentState(for: document)
        
        // The document state is normal.
        // Update the UI with unpresented peer changes, if any.
        //
        if document.documentState == .normal {
            navigationItem.rightBarButtonItem?.isEnabled = true
            handleConflictsItem.isEnabled = false
            if !document.unpresentedPeerChanges.isEmpty {
                let changes = document.unpresentedPeerChanges
                document.clearUnpresentedPeerChanges()
                updateCollectionView(with: changes)
            }
            return
        }
        // The document has conflicts but no error.
        // Update the UI with unpresented peer changes if any.
        //
        if document.documentState == .inConflict {
            navigationItem.rightBarButtonItem?.isEnabled = true
            handleConflictsItem.isEnabled = true
            if !document.unpresentedPeerChanges.isEmpty {
                let changes = document.unpresentedPeerChanges
                document.clearUnpresentedPeerChanges()
                updateCollectionView(with: changes)
            }
            return
        }
        // The document is in a closed state with no error. Clear the UI.
        //
        if document.documentState == .closed {
            navigationItem.rightBarButtonItem?.isEnabled = false
            handleConflictsItem.isEnabled = false
            title = ""
            var snapshot = DiffableImageSourceSnapshot()
            snapshot.appendSections([0])
            diffableImageSource.apply(snapshot)
            return
        }
        // The document has conflicts. Enable the toolbar item.
        //
        if document.documentState.contains(.inConflict) {
            handleConflictsItem.isEnabled = true
        }
        // The document is editingDisabled. Disable the UI for editing.
        //
        if document.documentState.contains(.editingDisabled) {
            navigationItem.rightBarButtonItem?.isEnabled = false
            handleConflictsItem.isEnabled = false
        }
    }

上面代碼顯示 當(dāng)打開(kāi)一個(gè)document后央串,可以監(jiān)聽(tīng)document的變化,包括沖突
不過(guò)解決沖突很簡(jiǎn)單的碗啄,Document 監(jiān)聽(tīng)狀態(tài)變化质和,當(dāng)檢測(cè)到?jīng)_突的時(shí)候,根據(jù)不同策略去解決稚字,比如再簡(jiǎn)單的根據(jù)修改時(shí)間饲宿,最新的保留,其他的刪除胆描。

  private func resolveConflictsAsynchronously(document: Document, completionHandler: ((Bool) -> Void)?) {
        DispatchQueue.global().async {
            NSFileCoordinator().coordinate(writingItemAt: document.fileURL,
                                           options: .contentIndependentMetadataOnly, error: nil) { newURL in
                let shouldRevert = self.pickLatestVersion(for: newURL)
                completionHandler?(shouldRevert)
            }
        }
    
    }

 private func pickLatestVersion(for documentURL: URL) -> Bool {
        guard let versionsInConflict = NSFileVersion.unresolvedConflictVersionsOfItem(at: documentURL),
              let currentVersion = NSFileVersion.currentVersionOfItem(at: documentURL) else {
            return false
        }
        var shouldRevert = false
        var winner = currentVersion
        for version in versionsInConflict {
            if let date1 = version.modificationDate, let date2 = winner.modificationDate,
               date1 > date2 {
                winner = version
            }
        }
        if winner != currentVersion {
            do {
                try winner.replaceItem(at: documentURL)
                shouldRevert = true
            } catch {
                print("Failed to replace version: \(error)")
            }
        }
        do {
            try NSFileVersion.removeOtherVersionsOfItem(at: documentURL)
        } catch {
            print("Failed to remove other versions: \(error)")
        }
        return shouldRevert
    }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘫想,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子昌讲,更是在濱河造成了極大的恐慌国夜,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件短绸,死亡現(xiàn)場(chǎng)離奇詭異车吹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鸠按,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饶碘,“玉大人目尖,你說(shuō)我怎么就攤上這事≡耍” “怎么了瑟曲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)豪治。 經(jīng)常有香客問(wèn)我洞拨,道長(zhǎng),這世上最難降的妖魔是什么负拟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任烦衣,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘花吟。我一直安慰自己秸歧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布衅澈。 她就那樣靜靜地躺著键菱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪今布。 梳的紋絲不亂的頭發(fā)上经备,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音部默,去河邊找鬼侵蒙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甩牺,可吹牛的內(nèi)容都是我干的蘑志。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼贬派,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼急但!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起搞乏,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤波桩,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后请敦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體镐躲,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年侍筛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萤皂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匣椰,死狀恐怖裆熙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情禽笑,我是刑警寧澤入录,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站佳镜,受9級(jí)特大地震影響僚稿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蟀伸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一蚀同、第九天 我趴在偏房一處隱蔽的房頂上張望缅刽。 院中可真熱鬧,春花似錦唤崭、人聲如沸拷恨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腕侄。三九已至,卻和暖如春芦疏,著一層夾襖步出監(jiān)牢的瞬間冕杠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工酸茴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留分预,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓薪捍,卻偏偏與公主長(zhǎng)得像笼痹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酪穿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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