使用 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 深入讨论了信号量。
我有一个包含物种和照片的应用程序。我正在将 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 深入讨论了信号量。