【发布时间】:2021-10-14 10:32:43
【问题描述】:
我正在使用 Swift/Xcode 构建一个原生 iOS 应用程序,该应用程序利用来自 3rd 方 API 的 JOSN 数据。一切正常,但 API 限制了您每小时可以调用 API 的次数。所以现在我正在构建一个函数,旨在创建一个每周数据库作为 CloudKit 中的 CK 资产,它将每 6 小时从 API 更新一次 JSON。这样,数据仍然是相对最新的,但也将 API 调用次数减少到每天仅 4 次。
注意:该应用正在 Production/TestFlight 中进行测试。
在创建新的 CKAsset 以在 CloudKit 中另存为新的 CKRecord 时,该功能正常工作。该函数还正确地从 CloudKit 下载和解码 CKAsset 以在应用程序中使用。
问题:每当函数检查 CKAsset 是否超过 6 小时时,它应该让任何用户通过下载更新的 JSON 文件并使用 CKModifyRecordsOperation 将其替换到 CKRecord 的 CKAsset 中来修改 CKRecord。问题是,每当其他用户尝试修改记录时,应用程序就会崩溃。
问题:为什么其他使用TestFlight的用户不能修改记录?使用 CKModifyRecordsOperation 我错了吗?
非常感谢任何帮助或建议!
-------------------代码/功能---------- -------------------
func fetchWeeklyPlayersDB() {
let semaphore = DispatchSemaphore.init(value: 0)
let thisWeek = getCurrentWeekID()
let current = Date()
// fetch current week PlayersDB from CK Database
publicDB.fetch(withRecordID: CKRecord.ID(recordName:thisWeek + "_Players")) { record, error in
// process record
if let r = record { // we know the playersDB has data
let modified = r.modificationDate
let expirationDate = modified?.addingTimeInterval(6*60*60) // add 6 hours
// if CK DB expirationDate is less than now, use it
if expirationDate! > current {
// not outdated - just process
if let data = r["DB"] {
// decode the JSON and set it to rawData
let d = readCKAsset(asset: data as! CKAsset)
let result = try? JSONDecoder().decode(dfsAPIData.self, from: d as! Data)
rawData = result
semaphore.signal()
}
} else { // CK DB is more than 6 hours old, call api and overwite data
// call API
let apiData = callAPI(week: findWeek())
// encode the api data as NSData
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(apiData)
// save data locally
if let path = saveJSON(data: jsonData) {
// convert result to CKASset using local save filepath
let asset:CKAsset = CKAsset.init(fileURL: path)
r["DB"] = asset
// Modify PlayersDB value in CKRecord
let modifyRecord = CKModifyRecordsOperation(recordsToSave: [r], recordIDsToDelete: nil)
modifyRecord.savePolicy = CKModifyRecordsOperation.RecordSavePolicy.allKeys
modifyRecord.qualityOfService = QualityOfService.userInitiated
modifyRecord.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
if error == nil {
// we did it!
print("PlayersDB Successfully overwritted with update api data")
// delete the file you created
deleteJSON(path: path)
rawData = apiData
semaphore.signal()
} else {
print("ERROR SAVING PlayersDB TO CK" + error!.localizedDescription)
// delete the file you created
deleteJSON(path: path)
// pull from the CK DB anyway so it fails softly
if let data = r["DB"] {
// decode the JSON and set it to rawData
let d = readCKAsset(asset: data as! CKAsset)
let result = try? JSONDecoder().decode(dfsAPIData.self, from: d as! Data)
rawData = result
semaphore.signal()
}
}
}
publicDB.add(modifyRecord)
}
}
catch {
print("Error Encoding JSON - WTF")
// if encoding fails - pull latest db instead to fail softly
if let data = r["DB"] {
// decode the JSON and set it to rawData
let d = readCKAsset(asset: data as! CKAsset)
let result = try? JSONDecoder().decode(dfsAPIData.self, from: d as! Data)
rawData = result
semaphore.signal()
}
}
}
}
// process error - DB doesnt exist, Call API and Create It
if let e = error {
// call API
let apiData = callAPI(week: findWeek())
// create record
let recordID = CKRecord.ID(recordName:thisWeek + "_Players")
let record = CKRecord(recordType: "WeeklyDB", recordID: recordID)
// encode the api data as NSData
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(apiData)
if let path = saveJSON(data: jsonData) {
// convert result to CKASset using local save filepath
let asset:CKAsset = CKAsset.init(fileURL: path)
record["DB"] = asset
// Save DB to CK
publicDB.save(record, completionHandler: {returnRecord, error in
if let err = error {
// something happened
print("ERROR SAVING PlayersDB TO CK" + err.localizedDescription)
} else {
// we did it!
print("PlayersDB Successfully overwritted with update api data")
// delete the file you just created
deleteJSON(path: path)
rawData = apiData
semaphore.signal()
}
})
}
}
catch {
print("Error Encoding JSON while saving api data to PlayersDB - WTF")
}
}
}
semaphore.wait()
return
}
【问题讨论】:
-
如果有人说他们的应用程序崩溃了,你不会问“在哪一行”吗?
-
... 有什么错误。
-
只是一些编码技巧; JSON 可以存储为字符串。无需转储到文件并使用 CKAsset。此外,您的更新代码不应存在于 fetch 函数中。进行提取,当它返回时,在单独的代码中根据需要进行更新。
-
谢谢@johnelemans!我很感激反馈。我将重新编写代码以分离出获取和更新功能。当我最初创建函数时,我在 CK 中使用了 Bytes 数据类型,我遇到了一个错误,提示超出了 1MB 的内存限制。因此,我改为将其切换到 CKAsset,以便为自己提供最大的内存限制。 CK 中的 String 数据类型与 Bytes 数据类型有类似的限制还是更大?我的 JSON 文件通常在 1-2mb 之间。
-
你说得对,超过 1MB 的记录需要使用 CKAsset。
标签: json swift api cloudkit ckasset