可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is there any limit to the result of a query to Cloudkit private default zone? I have no clue why I only receive first 100 records with the following query:
let p = NSPredicate(format: "(type == 'entered') AND (timestamp >= %@) AND (timestamp
Okay. As Edwin mention in the answer, the solution is to use CKQueryOperation to fetch the initial block of data then use the "cursor" in completionBlock to fire another operation. Here is an example:
UPDATE
func fetchBeacons(from:NSDate, to:NSDate) { let p = NSPredicate(value: true) let q = CKQuery(recordType: self.beaconRecordType, predicate: p) let queryOperation = CKQueryOperation(query: q) queryOperation.recordFetchedBlock = fetchedARecord queryOperation.queryCompletionBlock = { [weak self] (cursor : CKQueryCursor!, error : NSError!) in if cursor != nil { println("there is more data to fetch") let newOperation = CKQueryOperation(cursor: cursor) newOperation.recordFetchedBlock = self!.fetchedARecord newOperation.queryCompletionBlock = queryOperation.queryCompletionBlock self!.privateDatabase?.addOperation(newOperation) } } privateDatabase?.addOperation(queryOperation) } var i = 0 func fetchedARecord (record: CKRecord!) { println("\(NSDate().timeIntervalSinceReferenceDate*1000) \(++i)") }
回答1:
100 is the default limit for standard queries. That amount is not fixed. It can vary depending on the total iCloud load. If you want to influence that amount, then you need to use CKQueryOperation and set the resultsLimit like this: operation.resultsLimit = CKQueryOperationMaximumResults; That CKQueryOperationMaximumResults is the default and will limit it to 100 (most of the time). Don't set that value too high. If you want more records, then use the cursor of the queryCompletionBlock to continue reading more records.
回答2:
I use this code for my project to fetch all record from a record type, it's in objective c. I use "Entry" as desiredKeys.
+ (void)fetchRecordsWithType:(NSString *)recordType completionHandler:(void (^)(NSArray *records, NSError *error))completionHandler { NSPredicate *truePredicate = [NSPredicate predicateWithValue:YES]; CKQuery *query = [[CKQuery alloc] initWithRecordType:recordType predicate:truePredicate]; CKQueryOperation *queryOperation = [[CKQueryOperation alloc] initWithQuery:query]; queryOperation.desiredKeys = @[@"Entry"]; NSMutableArray *results = [NSMutableArray new]; queryOperation.recordFetchedBlock = ^(CKRecord *record) { [results addObject:record]; }; queryOperation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) { [self retrieveNextBatchOfQueryFromCursor:cursor results:results error:error completionHandler:completionHandler]; }; [[self CloudKitContainer].privateCloudDatabase addOperation:queryOperation]; } + (void)retrieveNextBatchOfQueryFromCursor:(CKQueryCursor *)cursor results:(NSMutableArray *)results error:(NSError *)error completionHandler:(void (^)(NSArray *records, NSError *error))completionHandler { // CloudKit apparently has query limit if (cursor != nil && !error) { CKQueryOperation *nextOperation = [[CKQueryOperation alloc] initWithCursor:cursor]; nextOperation.recordFetchedBlock = ^(CKRecord *record) { [results addObject:record]; }; nextOperation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error) { [self retrieveNextBatchOfQueryFromCursor:cursor results:results error:error completionHandler:completionHandler]; }; [[self CloudKitContainer].privateCloudDatabase addOperation:nextOperation]; } else { dispatch_async(dispatch_get_main_queue(), ^(void){ completionHandler(results, error); }); }}
回答3:
Another way of running it inside a function with a completion handler that won't stop until all records are fetched. This can be re-used by different different view controllers across the app.
Query
func cloudKitLoadRecords(result: (objects: [CKRecord]?, error: NSError?) -> Void){ // predicate var predicate = NSPredicate(value: true) // query let cloudKitQuery = CKQuery(recordType: "ClassName", predicate: predicate) // records to store var records = [CKRecord]() //operation basis let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase // recurrent operations function var recurrentOperationsCounter = 101 func recurrentOperations(cursor: CKQueryCursor?){ let recurrentOperation = CKQueryOperation(cursor: cursor!) recurrentOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in print("-> cloudKitLoadRecords - recurrentOperations - fetch \(recurrentOperationsCounter++)") records.append(record) } recurrentOperation.queryCompletionBlock = { (cursor:CKQueryCursor?, error:NSError?) -> Void in if ((error) != nil) { print("-> cloudKitLoadRecords - recurrentOperations - error - \(error)") result(objects: nil, error: error) } else { if cursor != nil { print("-> cloudKitLoadRecords - recurrentOperations - records \(records.count) - cursor \(cursor!.description)") recurrentOperations(cursor!) } else { print("-> cloudKitLoadRecords - recurrentOperations - records \(records.count) - cursor nil - done") result(objects: records, error: nil) } } } publicDatabase.addOperation(recurrentOperation) } // initial operation var initialOperationCounter = 1 let initialOperation = CKQueryOperation(query: cloudKitQuery) initialOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in print("-> cloudKitLoadRecords - initialOperation - fetch \(initialOperationCounter++)") records.append(record) } initialOperation.queryCompletionBlock = { (cursor:CKQueryCursor?, error:NSError?) -> Void in if ((error) != nil) { print("-> cloudKitLoadRecords - initialOperation - error - \(error)") result(objects: nil, error: error) } else { if cursor != nil { print("-> cloudKitLoadRecords - initialOperation - records \(records.count) - cursor \(cursor!.description)") recurrentOperations(cursor!) } else { print("-> cloudKitLoadRecords - initialOperation - records \(records.count) - cursor nil - done") result(objects: records, error: nil) } } } publicDatabase.addOperation(initialOperation) }
Usage
cloudKitLoadRecords() { (queryObjects, error) -> Void in dispatch_async(dispatch_get_main_queue()) { if error != nil { // handle error } else { // clean objects array if you need to self.objects.removeAll() if queryObjects!.count == 0 { // do nothing } else { // attach found objects to your object array self.objects = queryObjects! } } } }
回答4:
The simplest example for Swift:
func fetchServices(completion: ErrorHandler? = nil) { var records = [CKRecord]() let query = CKQuery(recordType: "Service", predicate: NSPredicate(value: true)) let queryOperation = CKQueryOperation(query: query) queryOperation.recordFetchedBlock = { record in records.append(record) } queryOperation.queryCompletionBlock = { cursor, error in self.fetchServices(with: cursor, error: error, records: records, completion: completion) } database.add(queryOperation) } private func fetchServices(with cursor: CKQueryCursor?, error: Swift.Error?, records: [CKRecord], completion: ErrorHandler? = nil) { 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.fetchServices(with: cursor, error: error, records: currentRecords, completion: completion) } database.add(queryOperation) } else { parseAndSaveServices(with: records, completion: completion) } }