CloudKit: Fetch all records with a certain record type?

后端 未结 6 811
鱼传尺愫
鱼传尺愫 2021-02-05 19:50

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         


        
相关标签:
6条回答
  • 2021-02-05 20:10

    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
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-05 20:10

    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)
        }
    }
    
    0 讨论(0)
  • 2021-02-05 20:12

    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"]);
        }
    }];
    
    0 讨论(0)
  • 2021-02-05 20:18

    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 ?? "")")
      })
    }
    
    0 讨论(0)
  • 2021-02-05 20:32

    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                
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-05 20:34

    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                                                                    
    0 讨论(0)
提交回复
热议问题