使用 CloudKit 处理完成

Handing a completion with CloudKit

我有一个包含物种和照片的应用程序。我正在将 cloudKit 添加到应用程序。我有一个可行的解决方案,但现在我需要添加一个完成处理程序,就像用户下载包含图像的新物种一样,这需要一些时间(当然取决于有多少图像)。但是,该应用程序允许用户在此过程的大部分时间里工作,因为它在后台运行。

问题是,如果图像尚未完全下载,并且指定应用程序的用户 select 自然会崩溃。

我需要输入一个完成处理程序(或者如果有人有更好的主意),它将允许我使用 activity 指示器,直到整个过程完成。我找到了一些示例,但它们没有考虑多个下载过程,例如我的图片和缩略图。

这是我的代码。请注意,我删除了一些不相关的代码以减少显示的数量。

func moveSpeciesFromCloud() {

    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: RemoteRecords.speciesRecord, predicate: predicate)

    CKDbase.share.privateDB.perform(query, inZoneWith: nil) {
        records, error in
        if error != nil {
            print(error!.localizedDescription)
        } else {
            guard let records = records else { return }
            for record in records {

                DispatchQueue.main.async {

                    self.remoteVersion = record[RemoteSpecies.remoteSpeciesVersion] as! Int
                    self.remoteSpeciesID = record[RemoteSpecies.remoteSpeciesID] as! Int
                    self.speciesDetail = AppDelegate.getUserDatabase().getSpeciesDetails(self.remoteSpeciesID)
                    self.localVersion = self.speciesDetail.version

                    // being sure that remote version is newer than local version
                    if self.localVersion >= self.remoteVersion {

                        print("Species version not newer")

                    } else {

                        self.commonNameLabel = record[RemoteSpecies.remoteCommonName] as! String
                        self.speciesLabel = record[RemoteSpecies.remoteSpeciesName] as! String
                        self.genusLabel = record[RemoteSpecies.remoteGenusName] as! String
                        self.groupLabel = record[RemoteSpecies.remoteGroupName] as! String
                        self.subGroupLabel = record[RemoteSpecies.remoteSubGroupName] as! String
                        self.speciesDetailsLabel = record[RemoteSpecies.remoteSpeciesDetails] as! String


                        // Here I sync records to SQLite, but removed code as not relevant.


                        // now syncing Photos, Thumbs, Groups, SubGroups and Favorties

                        self.syncPhotosFromCloud(self.remoteSpeciesID)
                        self.syncThumbsFromCloud(self.remoteSpeciesID)

                    }
                }
            }
        }
    }
}

这是缩略图的代码(图像是相同的过程)

func syncThumbsFromCloud(_ id: Int) {

    let predicate = NSPredicate(format: "thumbSpeciesID = \(id)")
    let query = CKQuery(recordType: RemoteRecords.thumbsRecord, predicate: predicate)

    CKDbase.share.privateDB!.perform(query, inZoneWith: nil)
    {
        records, error in

        if error != nil {
        print(error!.localizedDescription)
        } else {
            guard let records = records else { return }
            for record in records {
                DispatchQueue.main.async {

                    self.thumbName = (record.object(forKey: RemoteThumbs.remoteThumbName) as? String)!
                    self.thumbID = (record.object(forKey: RemoteThumbs.remoteThumbID) as? Int)!

                    if let asset = record[RemoteThumbs.remoteThumbFile] as? CKAsset,
                        let data = try? Data(contentsOf: (asset.fileURL)),
                        let image = UIImage(data: data)
                    {
                        let filemgr = FileManager.default
                        let dirPaths = filemgr.urls(for: .documentDirectory,
                                                    in: .userDomainMask)

                        let fileURL = dirPaths[0].appendingPathComponent(self.thumbName)

                        if let renderedJPEGData = image.jpegData(compressionQuality: 1.0) {
                            try! renderedJPEGData.write(to: fileURL)
                        }
                    }

                    // syncing records to SQLite
                    AppDelegate.getUserDatabase().syncThumbsFromCloudToSQLite(id: self.thumbID, name: self.thumbName, speciesID: id)
                }
            }
        }
    }
}

我在 SyncVC 上调用它:

@IBAction func syncCloudToDevice(_ sender: Any) {
    let cloudKit = CloudKit()
    cloudKit.moveSpeciesFromCloud()
    cloudKit.moveFavoritessFromCloud()
}

如果我遗漏了细节,请告诉我。

如有任何帮助,我们将不胜感激。

嗯,一般来说,RxSwift 这类事情很容易做到。你在.onSubscribe().onTerminated()中分别设置activity指标为on/off,你在subscriber/observer中得到最终结果准备好。专门针对 CloudKit,您可以使用 RxCloudKit 库。

你把图片做成单独的记录类型是有原因的吗?我只想将缩略图和完整照片添加到 Species 记录类型:

thumbnail = 字节 数据类型(最大 1MB)

photo = 资产数据类型(几乎无限)

这样,当您进行初始 Species 查询时,您将立即获得 thumbnail,然后您可以像当前一样访问 CKAsset,它将在后台下载.不需要第二个查询,这将使您的代码更简单。

我有点担心前面的两个答案都不能帮助回答你的问题。一个是要求你重构你的数据库,另一个是要求你依赖第三方库。

我的建议是把你的perform(_:inZoneWith:)变成一个同步操作,这样你就可以轻松地一个接一个地执行。例如:

func performSynchronously(query: CKQuery) throws -> [CKRecord] {

    var errorResult: Error?
    var recordsResult: [CKRecord]?
    let semaphore = DispatchSemaphore(value: 0)

    CKDbase.share.privateDB!.perform(query, inZoneWith: nil) { records, error in
        recordsResult = records
        errorResult = error
        semaphore.signal()
    }

    // Block this thread until `semaphore.signal()` occurs
    semaphore.wait()

    if let error = errorResult {
        throw error
    } else {
        return recordsResult ?? []
    }
}

确保您从后台线程调用它,以免阻塞您的 UI 线程! 例如:

// ... start your activity indicator

DispatchQueue(label: "background").async {

    do {
        let records1 = try performSynchronously(query: CKQuery...)
        // parse records1
        let records2 = try performSynchronously(query: CKQuery...)
        // parse records2

        DispatchQueue.main.async {
            // stop your activity indicator
        }

    } catch let e {
        // The error e occurred, handle it and stop the activity indicator
    }
}

当然,请将此代码作为如何使用信号量将异步操作转换为同步操作的灵感。 Here's a good article 深入讨论了信号量。