如何使用 iCloud 正确实现备份/恢复不同步,不是核心数据应用程序,不是基于文档的应用程序功能?

问题描述

我正在尝试使用 iCloud 备份(不同步)我的应用程序的用户数据和数据库。我想要实现的是按需备份和恢复功能。我创建了一个文件夹结构,如 ubiquityContainerRoot/userId/deviceid/Documents/ubiquityContainerRoot/userId/deviceid/Database/。这两个文件夹都将包含各种类型的文件

我需要一种方法来正确监控这些文件上传到 iCloud 的进度,即我需要知道所有文件 ubiquityContainerRoot/userId/deviceid/ 何时上传到 iCloud 以及整个进度(从文件夹的角度来看) ubiquityContainerRoot/userId/deviceid/).

到目前为止我所做的是

  • 用户数据和数据库复制到相应的泛在容器 URL 中
  • 创建了一个 NSMetaDataQuery 来监控 NSMetadataQueryUbiquitousDataScope
  • 手动跟踪每个文件的进度并在每次查询触发时计算整体进度

我的代码如下:

private var query: NSMetadataQuery?
private let notificationCenter = NotificationCenter.default
private var fileSizeMap = [URL: Double]()
private var progressMap = [URL: Double]()

func test() {
    let fileManager = FileManager.default

    let documentsSourceDirectory = fileManager
        .urls(for: .documentDirectory,in: .userDomainMask).first!
    print("documentsSourceDirectory: \(documentsSourceDirectory.path)")
    
    let databaseSourceDirectory = DATABASE_URL
    print("databaseSourceDirectory: \(databaseSourceDirectory.path)")

    let userId = "USER_ID"
    let deviceid = DEVICE_ID

    let iCloudContainerRoot = fileManager.url(forUbiquityContainerIdentifier: iCLOUD_IDENITIFiER)!
        .appendingPathComponent(userId)
        .appendingPathComponent(deviceid)
    print("iCloudContainerRoot: \(iCloudContainerRoot.path)")

    let documentsClouddirectory = iCloudContainerRoot
        .appendingPathComponent("Documents")
    let databaseClouddirectory = iCloudContainerRoot
        .appendingPathComponent("Database")

     do {
        try fileManager.copyAndOverwriteItem(at: documentsSourceDirectory,to: documentsClouddirectory)
        try fileManager.copyAndOverwriteItem(at: databaseSourceDirectory,to: databaseClouddirectory)
        print("copied data to iCloud.")
    } catch {
        fatalError(error.localizedDescription)
    }

    fileSizeMap = [:]
    progressMap = [:]
    populateFileSizeMap(cloudURL: documentsClouddirectory)
    populateFileSizeMap(cloudURL: databaseClouddirectory)

    createquery()
}

private func createquery() {
    let query = NSMetadataQuery()
    query.operationQueue = .main
    query.searchScopes = [NSMetadataQueryUbiquitousDataScope]
    query.predicate = nspredicate(format: "%K LIKE %@",NSMetadataItemFSNameKey,"*")

    self.query = query

    notificationCenter.addobserver(forName: .NSMetadataQueryDidFinishGathering,object: query,queue: query.operationQueue) {
        [weak self] (notification) in
        print("NSMetadataQueryDidFinishGathering")
        self?.queryDidFire(notification: notification)
    }

    notificationCenter.addobserver(forName: .NSMetadataQueryDidUpdate,queue: query.operationQueue) {
        [weak self] (notification) in
        print("NSMetadataQueryDidUpdate")
        self?.queryDidFire(notification: notification)
    }

    query.operationQueue?.addOperation {
        print("starting query")
        query.start()
        query.enableupdates()
    }
}

private func queryDidFire(notification: Notification) {
    guard let query = notification.object as? NSMetadataQuery else {
        print("Can not retrieve query from notification.")
        return
    }

    // without disabling the query when processing,app might crash randomly
    query.disableupdates()

    print("Result count: \(query.results.count)")
    for result in query.results {
        if let item = result as? NSMetadataItem {
            handeMetadataItem(item)
        } else {
            print("Not a Meta data item")
        }
    }

    // reenable updates on the query
    query.enableupdates()
}

private func handeMetadataItem(_ item: NSMetadataItem) {
    if let error = item.value(forAttribute: NSMetadataUbiquitousItemUploadingErrorKey) as? NSError {
        print("Item error: \(error.localizedDescription)")
        return
    }

    let srcURL = item.value(forAttribute: NSMetadataItemURLKey) as! URL
    print("Item URL: \(srcURL.path)")

    if let progress = item.value(forAttribute: NSMetadataUbiquitousItemPercentUploadedKey) as? Double {
        print("Item upload progress: \(progress)")
        handleProgress(for: srcURL,progress: progress)
    }

    if let isuploaded = item.value(forAttribute: NSMetadataUbiquitousItemIsuploadedKey) as? Bool,let isuploading = item.value(forAttribute: NSMetadataUbiquitousItemIsuploadingKey) as? Bool
        {
        print("Item isuploaded: \(isuploaded),isuploading: \(isuploading)")
    }
}

private func populateFileSizeMap(cloudURL: URL) {
    let fileURLs = try! FileManager.default.contentsOfDirectory(at: cloudURL,includingPropertiesForKeys: nil)

    for fileURL in fileURLs {
        do {
            let properties = try fileURL.resourceValues(forKeys: [.fileSizeKey])
            let fileSize = properties.fileSize ?? 0
            fileSizeMap[fileURL] = Double(fileSize)
        } catch {
            fatalError("Can not retrieve file size for file at: \(fileURL.path)")
        }
    }
}

private func handleProgress(for fileURL: URL,progress: Double) {
    guard let fileSize = fileSizeMap[fileURL] else { return }

    let prevUploadedSize = progressMap[fileURL] ?? 0
    let currUploadedSize = fileSize * (progress / 100.0)

    guard currUploadedSize >= prevUploadedSize else {
        fatalError("Metadata query reported less upload percentage than before")
    }

    progressMap[fileURL] = currUploadedSize

    let totalSizetoUpload = fileSizeMap.values.reduce(0) { $0 + $1 }
    print("totalSizetoUpload: \(totalSizetoUpload)")
    let totalSizeUploaded = progressMap.values.reduce(0) { $0 + $1 }
    print("totalSizeUploaded: \(totalSizeUploaded)")

    let uploadPercentage = (totalSizeUploaded / totalSizetoUpload) * 100
    print("uploadPercentage: \(uploadPercentage)")
}

现在我想知道

  • 这是使用 iCloud 实施备份/恢复的正确方法(此处未发布恢复代码)还是有更好的 API 来实现这一点?
  • 有没有办法使用 NSMetaDataQuery 监控整个文件夹的上传进度?
  • 如果用户进入后台并稍后进入前台,我如何知道上传进度,我是否需要部署另一个NSMetaDataQuery(只需创建一个查询而不再次复制数据)来监视更改或有没有办法备份完成后,做一些可以在后台唤醒我的应用的操作吗?

我也面临一些问题,非常感谢您的帮助

  • 有时,通过设置应用程序从设备上删除 iCloud 并不会清除另一台长期登录同一 iCloud 帐户的设备上的备份数据。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)