CKQuery из частной зоны возвращает только первые 100 CKRecords из CloudKit.

Есть ли ограничение на результат запроса к частной зоне по умолчанию Cloudkit? Я понятия не имею, почему я получаю только первые 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 для извлечения начального блока данных, а затем использовать «курсор» в завершенииBlock для запуска другой операции. Вот пример:

ОБНОВЛЕНИЕ

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

person CppChase    schedule 12.04.2015    source источник
comment
в порядке. Я нашел это. stackoverflow.com/a/27135836/893771 . Хотя кусок кода очень помог бы.   -  person CppChase    schedule 12.04.2015
comment
Этот ответ на аналогичный вопрос также показывает, как сохранить сильную ссылку на queryOperation, чтобы предотвратить завершение выборки, пока она не завершена. .   -  person Imanou Petit    schedule 19.08.2015
comment
это решение заставит операцию выполняться только 3 раза! Вместо этого используйте это: stackoverflow.com/a/31664231/530884   -  person Shaybc    schedule 04.04.2016


Ответы (5)


100 — это ограничение по умолчанию для стандартных запросов. Эта сумма не является фиксированной. Он может варьироваться в зависимости от общей загрузки iCloud. Если вы хотите повлиять на эту сумму, вам нужно использовать CKQueryOperation и установить resultsLimit следующим образом: operation.resultsLimit = CKQueryOperationMaximumResults; Этот CKQueryOperationMaximumResults используется по умолчанию и ограничивает его до 100 (в большинстве случаев). Не устанавливайте это значение слишком высоким. Если вам нужно больше записей, используйте курсор queryCompletionBlock, чтобы продолжить чтение дополнительных записей.

person Edwin Vermeer    schedule 12.04.2015
comment
Великий Эдвин. Я связал с вами ответ ранее :). У вас есть пример кода, как сделать «курсор»? Это сэкономило бы мне время. - person CppChase; 12.04.2015
comment
Извините, у меня нет хорошего готового образца для этого. Но это не сложно. Вы можете просто запустить новую операцию запроса следующим образом: var operation = CKQueryOperation(cursor: cursor) - person Edwin Vermeer; 12.04.2015
comment
Я принял ваш ответ и обновил свой вопрос примером кода. Танкс чувак. - person CppChase; 12.04.2015
comment
Отлично, но есть ли такой пример в Objective C? - person user3069232; 02.08.2015
comment
Спасибо @EdwinVermeer за отличное обсуждение и предоставленный пример кода @CPPChase! Для строки newOperation.queryCompletionBlock = queryOperation.queryCompletionBlock это не кажется правильным способом сделать это, но я не могу найти лучшего подхода - мысли? - person hyouuu; 02.10.2015

Я обновил код GuiSoySauce в Swift 4.2.

func cloudKitLoadRecords(result: @escaping (_ objects: [CKRecord]?, _ error: Error?) -> Void) {
    // predicate
    var predicate = NSPredicate(value: true)
    // query
    let cloudKitQuery = CKQuery(recordType: "recordType", 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)")
            recurrentOperationsCounter += 1
            records.append(record)
        }
        recurrentOperation.queryCompletionBlock = { (cursor: CKQueryOperation.Cursor?, error: Error?) -> Void in
            if ((error) != nil) {
                print("-> cloudKitLoadRecords - recurrentOperations - error - \(String(describing: error))")
                result(nil, error)
            } else {
                if cursor != nil {
                    print("-> cloudKitLoadRecords - recurrentOperations - records \(records.count) - cursor \(cursor!.description)")
                    recurrentOperations(cursor: cursor!)
                } else {
                    print("-> cloudKitLoadRecords - recurrentOperations - records \(records.count) - cursor nil - done")
                    result(records, nil)
                }
            }
        }
        publicDatabase.add(recurrentOperation)
    }
    // initial operation
    var initialOperationCounter = 1
    let initialOperation = CKQueryOperation(query: cloudKitQuery)
    initialOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
        print("-> cloudKitLoadRecords - initialOperation - fetch \(initialOperationCounter)")
        initialOperationCounter += 1
        records.append(record)
    }
    initialOperation.queryCompletionBlock = { (cursor: CKQueryOperation.Cursor?, error: Error?) -> Void in
        if ((error) != nil) {
            print("-> cloudKitLoadRecords - initialOperation - error - \(String(describing: error))")
            result(nil, error)
        } else {
            if cursor != nil {
                print("-> cloudKitLoadRecords - initialOperation - records \(records.count) - cursor \(cursor!.description)")
                recurrentOperations(cursor: cursor!)
            } else {
                print("-> cloudKitLoadRecords - initialOperation - records \(records.count) - cursor nil - done")
                result(records, nil)
            }
        }
    }
    publicDatabase.add(initialOperation)
}

использование

cloudKitLoadRecords() { (records, error) -> Void in
    if let error = error {
        // do something
    } else {
        if let records = records {
            // do something
        } else {
            // do something
        }
    }
}
person HirofumiYamamoto    schedule 16.12.2018
comment
Моя проблема заключалась в том, что я назначал блок завершения recurrentOperation тому же, что и исходный блок завершения, и новый блок завершения никогда не вызывался. Предоставление завершению recurrentOperation собственного блока завершения исправило это для меня. - person Kevin; 16.02.2021

Я использую этот код для своего проекта, чтобы получить всю запись из типа записи, это в цели c. Я использую «Вход» в качестве желаемых ключей.

+ (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); }); }}
person Ryde    schedule 03.01.2016

Другой способ запустить его внутри функции с обработчиком завершения, который не остановится, пока не будут получены все записи. Это может быть повторно использовано различными контроллерами представления в приложении.

Запрос

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!
                    }
                }
            }
        }
person GuiSoySauce    schedule 12.04.2016

Самый простой пример для 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)
    }
}
person Bartłomiej Semańczyk    schedule 10.01.2017