【问题标题】:CloudKit: Fetch all records with a certain record type?CloudKit:获取具有某种记录类型的所有记录?
【发布时间】:2015-04-08 18:48:42
【问题描述】:

我目前已在我的应用中设置了 CloudKit,因此我正在使用以下代码的帮助添加新记录,

CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"stringArray"];
CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Strings" recordID:recordID];
[record setObject:[NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil] forKey:@"stringArray"];
[_privateDatabase saveRecord:record completionHandler:nil];

但是,现在我希望能够获取具有相同记录类型“字符串”的所有记录,并将那些编译成 NSArray 的记录返回。我该怎么做呢?目前,我只知道如何使用recordID单独获取每条记录,这很麻烦,必须有更简单的方法。

[_privateDatabase fetchRecordWithID:recordID completionHandler:^(CKRecord *record, NSError *error) {
   if (error) {
      // Error handling for failed fetch from private database
   }
   else {
      NSLog(@"ICLOUD TEST: %@", [record objectForKey:@"stringArray"]);            
  }
}];

【问题讨论】:

  • 根据 CKQuery API Reference:“谓词基于格式字符串。您不能使用基于值或块的谓词。”我不认为总是这样,但目前这两个答案都不正确,因为他们使用NSPredicate(value:)
  • 我过去使用过它,但根据 Apple 自己的文档,任何这样做的人都应该知道 CloudKit 不支持它。 Apple 可以很好地做出会破坏该代码的更改,并且可以在不使用基于值或块的谓词的情况下完成。
  • 我认为这类似于通过哈希值识别枚举。你可以侥幸逃脱,直到声明顺序发生变化并且你的代码被破坏......基本上,制造商包括如何使用他们的 api 等的手册是有原因的......

标签: ios icloud cloudkit ckrecord


【解决方案1】:

啊,我明白了。使用下面的代码,我能够创建一个在数据库上运行的查询,然后在完成块中返回一个 NSArray,我循环通过它,并在 NSLog 中返回已保存键的值。

NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Strings" predicate:predicate];

[_privateDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
    for (CKRecord *record in results) {
        NSLog(@"Contents: %@", [record objectForKey:@"stringArray"]);
    }
}];

【讨论】:

  • 这不会返回所有记录,只是一个子集(通常默认限制为 100)
  • @MaxDesiatov 你说的有限是什么意思?如何获取全部?
  • 没关系,这里回答(100 个限制):linklink
  • 不起作用。出现错误:
【解决方案2】:

Swift 4 的解决方案, 展示了如何获取“YourTable”类型的所有记录,还打印System FieldCustom Field

let query = CKQuery(recordType: "YourTable", predicate: NSPredicate(value: true))
CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: nil) { (records, error) in
  records?.forEach({ (record) in

    // System Field from property
    let recordName_fromProperty = record.recordID.recordName
    print("System Field, recordName: \(recordName_fromProperty)")

    // Custom Field from key path (eg: deeplink)
    let deeplink = record.value(forKey: "deeplink")
    print("Custom Field, deeplink: \(deeplink ?? "")")
  })
}

【讨论】:

  • 此查询一次获取的对象不超过 100 个
【解决方案3】:

斯威夫特 5

在浏览了 SO 上的一堆帖子和解决方案后,我设法找到了一个适合我需求的解决方案,并且对于任何只想从 iCloud 获取给定类型的所有记录的人来说应该足够简单。

解决方案

使用 CKDatabase 扩展的解决方案引入了一种处理 CKQueryOperationcursor: CKQueryOperation.Cursor 的方法,以继续向 iCloud 询问更多记录。在这种方法中,我分派到后台队列,这样我就可以阻止它并等待操作完全完成,无论是在收到错误时还是在最后一批记录时。一旦信号量解锁队列,它就会继续调用主完成块并返回结果。我还在完成处理程序中利用了 Swift 的 Result 类型。

extension CKDatabase {

    func fetchAll(
        recordType: String, resultsLimit: Int = 100, timeout: TimeInterval = 60,
        completion: @escaping (Result<[CKRecord], Error>) -> Void
    ) {
        DispatchQueue.global().async { [unowned self] in
            let query = CKQuery(
                recordType: recordType, predicate: NSPredicate(value: true)
            )
            let semaphore = DispatchSemaphore(value: 0)
            var records = [CKRecord]()
            var error: Error?

            var operation = CKQueryOperation(query: query)
            operation.resultsLimit = resultsLimit
            operation.recordFetchedBlock = { records.append($0) }
            operation.queryCompletionBlock = { (cursor, err) in
                guard err == nil, let cursor = cursor else {
                    error = err
                    semaphore.signal()
                    return
                }
                let newOperation = CKQueryOperation(cursor: cursor)
                newOperation.resultsLimit = operation.resultsLimit
                newOperation.recordFetchedBlock = operation.recordFetchedBlock
                newOperation.queryCompletionBlock = operation.queryCompletionBlock
                operation = newOperation
                self?.add(newOperation)
            }
            self?.add(operation)

            _ = semaphore.wait(timeout: .now() + 60)

            if let error = error {
                completion(.failure(error))
            } else {
                completion(.success(records))
            }
        }
    }

}

用法

对于熟悉 Swift 闭包语法和Result 类型的任何人来说,使用该方法都相当简单。

let database: CKDatabase = ...
database.fetchAll(recordType: "User") { result in
    switch result {
        case .success(let users):
            // Handle fetched users, ex. save them to the database
        case .failure(let error):
            // Handle Error
        }
    }
}

【讨论】:

  • 你的添加功能在哪里?谢谢。
  • @Emmy 我在 CKDatabase 上调用 add(_ operation: CKDatabaseOperation) 方法
  • 为什么自选?这是在后来的 Swift 版本中改变的东西吗? Xcode 让我把它拿出来。
【解决方案4】:

这是 Swift 3.0 中的答案。

func myQuery()  {
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "tableName", predicate: predicate)

    publicDatabase.perform(query, inZoneWith: nil) { (record, error) in

        for record: CKRecord in record! {
            //...

            // if you want to access a certain 'field'.
            let name = record.value(forKeyPath: "Name") as! String                
        }
    }
}

【讨论】:

    【解决方案5】:

    followig 函数将返回请求记录类型的所有记录:

    let database = CKContainer(identifier: "container_here").privateCloudDatabase
    typealias RecordsErrorHandler = ([CKRecord], Swift.Error?) -> Void
    
    func fetchRecords(forType type: String, completion: RecordsErrorHandler? = nil) {
    
        var records = [CKRecord]()
    
        let query = CKQuery(recordType: type, predicate: NSPredicate(value: true))
        let queryOperation = CKQueryOperation(query: query)
        queryOperation.zoneID = CloudAssistant.shared.zone.zoneID
    
        queryOperation.recordFetchedBlock = { record in
            records.append(record)
        }
    
        queryOperation.queryCompletionBlock = { cursor, error in
    
            self.fetchRecords(with: cursor, error: error, records: records) { records in
                completion?(records, nil)
            }
        }
    
        database.add(queryOperation)
    }
    
    private func fetchRecords(with cursor: CKQueryCursor?, error: Swift.Error?, records: [CKRecord], completion: RecordsHandler?) {
    
        var currentRecords = records
    
        if let cursor = cursor, error == nil {
    
            let queryOperation = CKQueryOperation(cursor: cursor)
    
            queryOperation.recordFetchedBlock = { record in
                currentRecords.append(record)
            }
    
            queryOperation.queryCompletionBlock = { cursor, error in
                self.fetchRecords(with: cursor, error: error, records: currentRecords, completion: completion)
            }
    
            database.add(queryOperation)
    
        } else {
            completion?(records)
        }
    }
    

    【讨论】:

      【解决方案6】:

      在尝试获取所有记录并了解 Cloudkit 存储的结构和细节时,我发现在调试期间提供以下功能很有用。这使用信号量来保留用于打印的数据结构。可能有一种更优雅的方法可以做到这一点,但这很有效!

      //
      // Print a list of records in all zones in all databases
      //
      func printRecordsInContainers() {
      
          let myContainer = CKContainer.default()
          // Edit the following dictionary to include any known containers and possible record types
          let containerRecordTypes: [CKContainer: [String]] = [ myContainer: ["MyRecordType", "OldRecordType", "MyUser", "PrivateInfo"] ]
          let containers = Array(containerRecordTypes.keys)
      
          for containerz in containers {
              let databases: [CKDatabase] = [containerz.publicCloudDatabase, containerz.privateCloudDatabase, containerz.sharedCloudDatabase]
      
              for database in databases {
                  var dbType = "<None>"
                  if database.databaseScope.rawValue == 1 { dbType = "Public" }
                  if database.databaseScope.rawValue == 2 { dbType = "Private" }
                  if database.databaseScope.rawValue == 3 { dbType = "Shared" }
      
                  //print("\(database.debugDescription)")
                  print("\n\n\n? ---------------------------------------------------------------------------------------------------")
                  print("? ----- Container: \(containerz.containerIdentifier ?? "??") ----- Database: \(dbType)")
                  let semaphore1 = DispatchSemaphore(value: 0)     // Initiate semaphore1 to wait for closure to return
      
                  database.fetchAllRecordZones { zones, error in
                      if let error = error {
                          print("? Error Fetching Zones: \(error.localizedDescription)")
                      }
                      else if let zones = zones {
                          print("~~~~ \(zones.count) : \(zones)")
                          for zone in zones {
                              print("----- Zone ID: \(zone.zoneID)\n")
                              for recordType in containerRecordTypes[container] ?? [] {
                                  print("[  Record Type: \(recordType.description)  ]")
                                  let query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
      
                                  let semaphore = DispatchSemaphore(value: 0)     // Initiate semaphore to wait for closure to return
                                  database.perform(query, inZoneWith: zone.zoneID) { records, error in
                                      if let error = error {
                                          print("? Error in Record Query: \(error.localizedDescription)")
                                      }
                                      else if let records = records {
                                          printRecordDescriptions(records)
                                      }
                                      semaphore.signal()      // Send semaphore signal to indicate closure is complete
                                  }
                                  semaphore.wait()            // Wait for semaphore to indicate that closure is complete
                              }
                          }
                      }
                      else {
                          print("? Error in fetchAllRecordZones")
                      }
                      semaphore1.signal()      // Send semaphore1 signal to indicate closure is complete
                  }
                  semaphore1.wait()            // Wait for semaphore1 to indicate that closure is complete
              }
          }
      }
      class func printRecordDescriptions(_ records: [CKRecord]) {
          print("Records and Contents List:")
          for record in records {
              print("? Record: \(record.recordID)")
              for key in record.allKeys() {
                  print("    Key - \(key)")
              }
          }
          print("Record List End\n")
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多