Ios CloudKit:获取具有特定记录类型的所有记录?

Ios CloudKit:获取具有特定记录类型的所有记录?,ios,icloud,cloudkit,ckrecord,Ios,Icloud,Cloudkit,Ckrecord,我目前已在我的应用程序中设置了CloudKit,以便我使用以下代码添加新记录 CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"stringArray"]; CKRecord *record = [[CKRecord alloc] initWithRecordType:@"Strings" recordID:recordID]; [record setObject:[NSArray arrayWithObjects:@

我目前已在我的应用程序中设置了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];
但是,现在我希望能够获取相同记录类型“Strings”的所有记录,并返回编译到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"]);            
  }
}];

啊,我明白了。使用下面的代码,我能够创建一个在数据库上运行的查询,然后在完成块中返回一个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"]);
    }
}];

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

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)
    }
}

以下是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                
        }
    }
}

Swift 4的解决方案, 显示如何获取类型为“YourTable”的所有记录,并打印
系统字段
自定义字段

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 ?? "")")
  })
}

在尝试获取所有记录并理解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\nSwift 5

After looking through a bunch of posts and solutions on SO I have managed to come with a solution that suits my needs and should be simple enough for anyone that just wants to fetch all of their records of given type from iCloud.

Solution

The solution that uses an extension to the CKDatabase to introduce a method that handles the
cursor: CKQueryOperation.Cursor
of
CKQueryOperation
to continue asking iCloud for more records. In this approach I dispatch to the background queue so I can block it and wait for the operation to be finished completely, either on receiving an error or with the last batch of records. Once the semaphore unlocks the queue it proceeds with calling the main completion block with the result. Also I am taking advantage of Swift's
Result
type in the completion handler.

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))
            }
        }
    }

}
//
//打印所有数据库中所有区域的记录列表
//
func printRecordsInContainers(){
让myContainer=CKContainer.default()
//编辑以下词典以包括任何已知容器和可能的记录类型
let containerRecordTypes:[CKContainer:[String]]=[myContainer:[“MyRecordType”、“OldRecordType”、“MyUser”、“PrivateInfo”]]
let containers=Array(containerRecordTypes.keys)
用于集装箱中的集装箱Z{
let数据库:[CKDatabase]=[containerz.publicCloudDatabase,containerz.privateCloudDatabase,containerz.sharedCloudDatabase]
用于数据库中的数据库{
var dbType=“”
如果database.databaseScope.rawValue==1{dbType=“Public”}
如果database.databaseScope.rawValue==2{dbType=“Private”}
如果database.databaseScope.rawValue==3{dbType=“Shared”}
//打印(“\(database.debugDescription)”)

打印(“\n\n\nSwift 5

在浏览了一大堆文章和解决方案之后,我终于找到了一个适合我需要的解决方案,对于那些只想从iCloud获取所有给定类型记录的人来说,这个解决方案应该足够简单

解决方案 该解决方案使用CKDatabase的扩展引入一种方法来处理
cursor:CKQueryOperation。cursor
of
CKQueryOperation
继续向iCloud请求更多记录。在这种方法中,我将其分派到后台队列,以便阻止它并等待操作完全完成在接收到错误或最后一批记录时。一旦信号量解锁队列,它将继续使用结果调用主完成块。此外,我还利用了Swift在完成处理程序中的
result
类型

扩展数据库{ func fetchAll( 记录类型:字符串,结果限制:Int=100,超时:TimeInterval=60, 完成:@escaping(Result)->Void ) { DispatchQueue.global().async{[unowned self]位于 let query=CKQuery( 记录类型:记录类型,谓词:NSPredicate(值:true) ) 让信号量=分派信号量(值:0) var记录=[CKRecord]() var错误:错误? var operation=CKQueryOperation(查询:query) operation.resultsLimit=resultsLimit operation.recordFetchedBlock={records.append($0)} operation.queryCompletionBlock={(游标,错误)位于 guard err==nil,让cursor=cursor else{ 错误=错误 信号量 返回 } 让newOperation=CKQueryOperation(游标:游标) newOperation.resultsLimit=operation.resultsLimit newOperation.recordFetchedBlock=operation.recordFetchedBlock newOperation.queryCompletionBlock=operation.queryCompletionBlock 操作=新操作 self?.add(新操作) } self?.add(操作) _=信号量。等待(超时:.now()+60) 如果let error=error{ 完成(.failure(error)) }否则{ 完成(.success(记录)) } } } }
用法 对于熟悉Swift闭包语法和
Result
type的人来说,使用该方法相当简单

let数据库:CKDatabase=。。。
database.fetchAll(记录类型:“User”){结果为
切换结果{
成功案例(让用户参与):
//处理获取的用户,例如,将其保存到数据库
案例。失败(let错误):
//处理错误
}
}
}

但这不会返回所有记录,只返回一个子集(默认情况下通常限制为100)@MaxDesiatov您对limited的意思是什么?如何获取所有记录?不要紧,这里回答了它(100个限制):并且不起作用。获取错误:根据CKQuery API参考:谓词基于格式字符串。不能使用值或基于块的谓词。“我不认为总是这样,但目前两个答案都不正确,因为它们使用
NSPredicate(值:)
我过去曾使用过它,但根据苹果公司自己的文档,任何这样做的人都应该知道CloudKit不支持它。苹果公司很可能会做出改变,破坏代码,并且可以使用基于值或块的谓词来完成。我认为这类似于通过