Swift CloudKit 和 CKQuery : how to iteratively retrieve records when queryResultBlock returns a query cursor

标签 swift asynchronous cloudkit ckquery

我正在使用 CloudKit 通过 CKQuery 从私有(private)数据库检索记录,并在异步函数中使用 CKQueryOperation.queryResultBlock。我发现了几个使用 queryCompletionBlock 的示例,但它已被弃用并被 queryResultBlock 取代,关于如何实现它的可用文档很少。只要不返回查询完成游标(<=100 条记录),我的函数就可以很好地工作,但我无法弄清楚如何迭代它。

这是我正在使用的代码:

public func queryRecords(recordType: CKRecord.RecordType, predicate: NSPredicate) async throws -> [CKRecord] {
    var resultRecords: [CKRecord] = []
    let db = container.privateCloudDatabase
    let query = CKQuery(recordType: recordType, predicate: predicate)
    let operation = CKQueryOperation(query: query)
    let operationQueue = OperationQueue() // for > 100 records
    operationQueue.maxConcurrentOperationCount = 1 // for > 100 records
    operation.zoneID = zoneID
    debugPrint("query for recordType=\(recordType) in zone \(zoneID.zoneName) with predicate \(predicate)")
    return try await withCheckedThrowingContinuation { continuation in
        operation.queryResultBlock = { result in
            switch result {
            case .failure(let error):
                debugPrint(error)
                continuation.resume(throwing: error)
            case .success(let ckquerycursor):
                debugPrint("successful query completion after \(resultRecords.count) record(s) returned")
                if let ckquerycursor = ckquerycursor {
                    debugPrint("***** received a query cursor, need to fetch another batch! *****")
                    let newOperation = CKQueryOperation(cursor: ckquerycursor)  // for > 100 records
                    newOperation.queryResultBlock = operation.queryResultBlock // for > 100 records
                    newOperation.database = db // for > 100 records
                    operationQueue.addOperation(newOperation) // for > 100 records
                }
                continuation.resume(returning: resultRecords)
            }
        }
        operation.recordMatchedBlock = { (recordID, result1) in
            switch result1 {
            case .failure(let error):
                debugPrint(error)
            case .success(let ckrecord):
                resultRecords.append(ckrecord)
            }
        }
        db.add(operation)
    }
}

我尝试实现类似示例中的代码,但没有成功:上面的代码导致 fatal error “SWIFT TASK CONTINUATION MISUSE”作为行

continuation.resume(returning: resultRecords)

显然被多次调用(非法)。用“//for > 100 个记录”注释的行代表我添加到迭代的代码;对于 100 或更少的记录集,其他一切都可以正常工作。

我是否需要迭代调用 queryRecords 函数本身,传递查询游标(如果存在),或者是否可以像我在此处尝试的那样将迭代操作添加到队列中?

如果有人在使用queryResultBlock(不是已弃用的queryCompletionBlock)之前做过此操作,请帮忙! 谢谢!

最佳答案

在 Swift 5.5 中不需要 queryResultBlock

我使用它是因为我的 CKRecord 类型的命名始终与其 Swift 对应类型相同。如果需要,您可以将 recordType: "\(Record.self)" 替换为您的 recordType

public extension CKDatabase {
  /// Request `CKRecord`s that correspond to a Swift type.
  ///
  /// - Parameters:
  ///   - recordType: Its name has to be the same in your code, and in CloudKit.
  ///   - predicate: for the `CKQuery`
  func records<Record>(
    type _: Record.Type,
    zoneID: CKRecordZone.ID? = nil,
    predicate: NSPredicate = .init(value: true)
  ) async throws -> [CKRecord] {
    try await withThrowingTaskGroup(of: [CKRecord].self) { group in
      func process(
        _ records: (
          matchResults: [(CKRecord.ID, Result<CKRecord, Error>)],
          queryCursor: CKQueryOperation.Cursor?
        )
      ) async throws {
        group.addTask {
          try records.matchResults.map { try $1.get() }
        }
        
        if let cursor = records.queryCursor {
          try await process(self.records(continuingMatchFrom: cursor))
        }
      }

      try await process(
        records(
          matching: .init(
            recordType: "\(Record.self)",
            predicate: predicate
          ),
          inZoneWith: zoneID
        )
      )
      
      return try await group.reduce(into: [], +=)
    }
  }
}

关于Swift CloudKit 和 CKQuery : how to iteratively retrieve records when queryResultBlock returns a query cursor,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71200053/

相关文章:

c# - API 中返回 Task 的方法是否应该以 Task 或 Async 结尾

ios - 使用 CKSubscription/CKNotificationInfo 显示通知特定消息?

ios - 获取与 CloudKit 查询匹配的记录数

xcode - 暴露给 Objective-C 代码时 Swift 类的名称不遵循 @objc 重命名

swift - 将 AVTimedMetadataGroup 添加到 AVMutableComposition 以在(临时)视频中创建章节标记

asynchronous - Flutter/Dart - 调用一个 Future<String> 的函数......但只需要返回一个 String

ios - 云包 "Your request contains more than the maximum number of items in a single request (400)"

ios - 在 SWIFT 4.1 中从 Firebase 检索数据时出现 'subscript' 的不明确使用和无法调用非函数类型 'AnyObject' 的值错误

ios - 具有类似 scaleAspectFill 行为的 UINavigationBar 背景图像

javascript - 对 SQL 查询进行排队