CKQuery из private zone возвращает только первые 100 записей CKRecords из CloudKit
Существует ли ограничение на результат запроса кCloudkit private default zone? Я понятия не имею, почему я получаю только первые 100 записей со следующим запросом:
let p = NSPredicate(format: "(type == 'entered') AND (timestamp >= %@) AND (timestamp <= %@)", from, to)
let q = CKQuery(recordType: self.beaconRecordType, predicate: p)
q.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
self.privateDatabase?.performQuery(q, inZoneWithID: nil, completionHandler: { results, error in
//count = 100
println(results.count)
}
Хорошо. Как отметил Эдвин в ответе, решение состоит в том, чтобы использовать CKQueryOperation для извлечения начального блока данных, а затем использовать "курсор" в блоке completionBlock для запуска другой операции. Вот пример:
Обновить
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)")
}
4 ответа:
100-это ограничение по умолчанию для стандартных запросов. Эта сумма не является фиксированной. Он может варьироваться в зависимости от общей загрузки iCloud. Если вы хотите повлиять на эту сумму, то вам нужно использовать CKQueryOperation и установить resultsLimit следующим образом: операция.resultsLimit = CKQueryOperationMaximumResults; Что CKQueryOperationMaximumResults является значением по умолчанию и будет ограничивать его до 100 (большую часть времени). Не устанавливайте это значение слишком высоко. Если вы хотите получить больше записей, то используйте курсор queryCompletionBlock для продолжения чтения дополнительных записей.
Я использую этот код для моего проекта, чтобы извлечь все записи из типа записи, это в objective c. я использую "запись" в качестве 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); }); }}
Еще один способ запустить его внутри функции с обработчиком завершения, который не остановится, пока не будут извлечены все записи. Это может быть повторно использовано различными контроллерами различных видов в приложении.
Запрос
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) }
Использование
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! } } } }
Самый простой пример для 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) } }