CKFetchRecordsOperation + CKQueryOperations ... что мне не хватает?

Успел собрать CKFetchRecordsOperation после долгих поисков примера кода; и вот оно... но я должно быть что-то пропустил. Не поймите меня неправильно, это доставляет удовольствие... но...

Для выполнения операции CKFetchRecordsOperation вам нужен NSArray из CKRecordIDs; чтобы получить NSArray из CKRecordID, вам нужно выполнить CKQuery, с помощью которого вы можете создать свой NSArray из CKRecordID.

Но подождите минуту, процесс извлечения CKRecordID использует CKQuery, через который я мог бы просто загрузить CKRecords?

Как вы получаете NSArray CKRecordIDs, если не с CKQuery?

-(void)doSingleBeaconsDownload
{
    CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:@"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
    NSPredicate *predicatex = [NSPredicate predicateWithFormat:@"iBeaconConfig = %@",iBeaconsConfirmed.giReferenceID];
    CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Running" predicate:predicatex];

    [self.delegate performSelectorOnMainThread:@selector(processCompleted:) withObject:@"Downloading Configuration ..." waitUntilDone:YES];

    [publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {

        if (error) {
            NSLog(@"Batch Download Error iCloud error %@",error);
        }
        else {
            NSMutableArray *rex2download = [[NSMutableArray alloc] init];
            for (CKRecord *rex in results) {
                [rex2download addObject:rex.recordID];
            }

            CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:rex2download];

           /* fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
                if (error) {
                    // Retain the record IDs for failed fetches
                }
                else {
                   // Do something with each record downloaded
                }
            };*/

            fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded) {
                if (error) {
                    // damn ...
                } else {
                    NSLog(@"Downloaded %f", recordsDownloaded);
                }
            };
            fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
                if (error) {
                    // Failed to fetch all or some of the records
                }
                else {
                    for(CKRecord *record in results) {
                        NSLog(@"Fini download %lu",(unsigned long)[recordsByRecordID count]);
                    }
                    [self.delegate performSelectorOnMainThread:@selector(beaconsDownloaded:) withObject:noOf waitUntilDone:YES];
                }
            };
            [publicDatabase addOperation:fetchRecordsOperation];
        }
    }];
}

3 ответа

Решение

Из документации Apple: объект CKFetchRecordsOperation извлекает объекты CKRecord (чьи идентификаторы вы уже знаете) из iCloud.

CKQueryOperation используется для извлечения CKRecords из iCloud на основе некоторого запроса, поэтому вы можете получить их, даже если вы не знаете их recordID. CKFetchRecordsOperation используется ТОЛЬКО, когда у вас есть CKRecordID. Вы можете создать CKRecordID без доступа к iCloud и хранить их в любом локальном хранилище, которое у вас есть.

Хороший вариант использования, который я использую для такого рода операций, - это когда вы хотите изменить CKRecord, вам нужно сначала извлечь его из iCloud (используя CKFetchRecordsOperation), а затем сохранить его обратно, используя CKModifyRecordsOperation.

Посмотрите два видео WWDC 2014 на CloudKit, которые объясняют это довольно хорошо.

Спасибо за вашу помощь! Мне удалось создать CKQueryOperation в коде, но... но мой код скоро станет нечитаемым со многими из этих вложенных циклов? Конечно, есть более элегантный способ связать операции CKQuery/Fetch/Modify; пробовал зависимость, но чего-то еще не хватает?

-(void)doSingleBeaconsDownload

{

[self.delegate performSelectorOnMainThread:@selector(processCompleted:) withObject:@"Downloading Configuration ..." waitUntilDone:YES];



CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:@"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];

NSPredicate *predicatex = [NSPredicate predicateWithFormat:@"iBeaconConfig = %@",iBeaconsConfirmed.giReferenceID];



CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Running" predicate:predicatex];

CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];

queryOp.desiredKeys = @[@"record.recordID.recordName"];

queryOp.resultsLimit = CKQueryOperationMaximumResults;

NSMutableArray *rex2download = [[NSMutableArray alloc] init];



queryOp.recordFetchedBlock = ^(CKRecord *results)

{

   [rex2download addObject:results.recordID];

};



queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)

{

// Cursor it seems contains a reference to a second call to it [required] if you try download more then 100 records       

    if (error) {

        NSLog(@"Batch Download Error iCloud error %@",error);

    }

    else {

        CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:rex2download];

       fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {

            if (error) {

                // Retain the record IDs for failed fetches

            }

            else {

              // Do something ..

            }

        };



        fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded) {

            if (error) {

                // damn

            } else {

                NSLog(@"Downloaded X");

            }

        };



        fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {

            if (error) {

                // Failed to fetch all or some of the records

            }

            else {

                … Final clean up

            }

        };

        [publicDatabase addOperation:fetchRecordsOperation];

    }

 };



 [publicDatabase addOperation:queryOp];

}

Спасибо Гарри; Вот мое третье и последнее рабочее решение; использовал глобальную переменную singleton для передачи данных между двумя операциями CKQueryOperation; не знаю, является ли это лучшей / хорошей практикой, но это работает

... Жаль, что вы не можете использовать что-то вроде этого...

[fetchRecordsOperation addDependency:queryOp]; &
[queue fetchRecordsOperation]; (doesn't compile)

Было бы гораздо более чистым решением... в любом случае вот V3 для полноты..

-(void)doSingleBeaconsDownloadV3
{

NSLog(@"doQuery executing");
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:@"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:@"iBeaconConfig = %@",iBeaconsConfirmed.giReferenceID];

CKQuery *query = [[CKQuery alloc] initWithRecordType:@"Running" predicate:predicatex];
CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = @[@"record.recordID.recordName"];
queryOp.resultsLimit = CKQueryOperationMaximumResults;
//NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO];
//query.sortDescriptors = @[sortDescriptor];

queryOp.recordFetchedBlock = ^(CKRecord *results)
{
    [iBeaconsConfirmed.giRex2Download addObject:results.recordID];
    NSLog(@"fetched %lu",[iBeaconsConfirmed.giRex2Download count]);
};

queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
    if (error) {
        NSLog(@"Batch Download Error iCloud error %@",error);
    } else {
       NSLog(@"fetched %lu",[iBeaconsConfirmed.giRex2Download count]);
        [self DoFetchV2];
    }
};
[publicDatabase addOperation:queryOp];
}

-(void)DoFetchV2
{

NSLog(@"dofetch executing %lu",[iBeaconsConfirmed.giRex2Download count]);

CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:@"iCloud.cqd.ch.BeaconBrowser"] publicCloudDatabase];
CKFetchRecordsOperation *fetchRecordsOperation = [[CKFetchRecordsOperation alloc] initWithRecordIDs:iBeaconsConfirmed.giRex2Download];
fetchRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, CKRecordID *recordID, NSError *error) {
    if (error) {
        // Retain the record IDs for failed fetches
    }
    else {

        // Do something useful with data
    }
};

fetchRecordsOperation.perRecordProgressBlock = ^(CKRecordID *record, double recordsDownloaded)
{
    NSLog(@"Downloaded X");
};

fetchRecordsOperation.fetchRecordsCompletionBlock = ^(NSDictionary *recordsByRecordID, NSError *error) {
    if (error) {
        // Failed to fetch all or some of the records
    } else {
        NSLog(@"Fini download %lu",(unsigned long)[recordsByRecordID count]);
    }
};
 [publicDatabase addOperation:fetchRecordsOperation];

}

Другие вопросы по тегам