【问题标题】:Using CKAsset to store API JSON and Updating every 6 Hours使用 CKAsset 存储 API JSON 并每 6 小时更新一次
【发布时间】: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


【解决方案1】:

所以这个答案的解决方案分为三个部分,因为我基本上遇到了三个问题。

一个 - 当我第一次创建我的函数时,我使用 CK 数据类型“字节”和“字符串”来保存我的 JSON,但是我收到了一个错误,因为我的 JSON 的大小超过了这些数据类型符合的 1mb 限制.我没有意识到大小限制导致错误,相反我认为是保存功能不起作用,所以我改用 CKModifyRecord 来尝试保存。一旦失败,我注意到大小限制并切换到使用 CKAsset 来保存数据库。这修复了限制,但我在保存或修改 CK 记录的资产时仍然收到错误。

二 - 由于大小不再是问题,我进行了一些挖掘,结果发现 CloudKit 中的安全权限拒绝其他用户保存他们没有创建的记录。为了解决这个问题,我进入 CK Dashboard 并在“Schema”部分下找到了“Security Roles”。我将角色从“创建者”切换到“iCloud”,以允许所有连接的 iCloud 用户编辑记录。

三 - 我遇到的最后一个问题是识别我从 TestFlight 测试人员那里得到的错误。在此之前,我从未尝试过访问 TestFlight 用户的崩溃日志,尽管这是一个漫长而复杂的过程。不——它内置在 Xcode 中,开箱即用,不需要配置。 (1) 打开Xcode,点击顶部菜单中的Window > Organizer,打开Organizer Window。

(2) 您可以在此处查看您已归档并提交到试飞的每个构建版本的所有崩溃日志。而且,您甚至可以在 Xcode 中运行它们,通过单击“在 Xcode 中打开”按钮来查找导致程序崩溃的行/错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 2016-09-12
    • 2021-01-09
    • 1970-01-01
    • 2012-07-08
    相关资源
    最近更新 更多