0

我正在使用 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

}

4

1 回答 1

0

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

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

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

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

在此处输入图像描述

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

在此处输入图像描述

于 2021-08-12T17:28:04.093 回答