CloudKit: Fetch all records with a certain record type?

你。 提交于 2020-05-09 22:52:41

问题


I have currently got CloudKit set up in my app so that I am adding a new record using the help of the following code below,

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];

However, now I would like to be able to fetch ALL records that are of the same record type, "Strings," and return those compiled into an NSArray. How would I go about doing that? Currently, all I have figured out is how to fetch each record individually, using a recordID, which is a hassle, there must be an easier way.

[_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"]);            
  }
}];

回答1:


Aaaand, I've got it. Using the code below, I was able to create a query to run on the database, to then return an NSArray in the completion block, which I looped through, and returned the value for the saved key in an 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"]);
    }
}];



回答2:


Solution for Swift 4, shows how to fetch all the records of type "YourTable", also prints System Field and Custom 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 ?? "")")
  })
}



回答3:


Here's the answer in 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                
        }
    }
}



回答4:


The followig function will return ALL records for requested record type:

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



回答5:


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

}

Usage

Using the method is fairly straight forward for anyone familiar with Swift's closure syntax and Result type.

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



回答6:


In trying to fetch all records, and understand the structure and details of Cloudkit storage, I found it useful to have the following function available during debug. This uses semaphores to retain the data structure for printing. There may be a more elegant way to do this but this works!

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


来源:https://stackoverflow.com/questions/28402846/cloudkit-fetch-all-records-with-a-certain-record-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!