Managed to cobble together a CKFetchRecordsOperation after much searching for sample code; and here it is... but I must have missed something. Don't get me wrong it works a treat... but...
To execute a CKFetchRecordsOperation you need an NSArray of CKRecordIDs; to get a NSArray of CKRecordIDs, you need to execute CKQuery thru which you can build your NSArray of CKRecordIDs.
But wait a minute, the process of extracting the CKRecordIDs uses a CKQuery, thru which I could simply download the CKRecords anyway?
How do you get your NSArray of CKRecordIDs if not with a 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];
}
}];
}
From Apple Documentation: A CKFetchRecordsOperation object retrieves CKRecord objects (whose IDs you already know) from iCloud.
A CKQueryOperation is used to retrieve CKRecords from iCloud based on some Query, so you can get them even if you do not know their recordIDs. A CKFetchRecordsOperation is used ONLY when you have the CKRecordIDs. You can create a CKRecordID without accessing iCloud, and you can store them in any local storage you have.
A good use case, which I use for this kind of operation, is when you want to modify a CKRecord, you need to first retrieve it from iCloud (using CKFetchRecordsOperation) and then save it back using CKModifyRecordsOperation.
Have a look at the two WWDC 2014 Videos on CloudKit that explain this pretty well.
Thanks for your help! I managed to craft an CKQueryOperation into the code, but ... but my code is soon going be become unreadable with many more of these nested loops? Surely there is a more elegant way to link CKQuery/Fetch/Modify operations; tried dependancies but missing something still ?
-(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];
}
Thanks Harry; Here is my third and final working solution; used a singleton global variable to pass the data between the two CKQueryOperations; don't know if that is best/good practice, but it works
... seems a pity you cannot use something like this ...
[fetchRecordsOperation addDependency:queryOp]; &
[queue fetchRecordsOperation]; (doesn't compile)
Would be a far cleaner solution... anyway here is V3 for completeness..
-(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];
}
Related
I use a method to fetch all CloudKit entries at the first startup of the app.
This worked fine for a while but recently after the first batch of 100 results I receive following error:
CKError 0x15cdd5d90: "Server Rejected Request" (15/2000); server
message = "Internal server error";
- (void)fetchAllCKEntries{
if (self.fQueryCursor) {
fQueryOperation = [[CKQueryOperation alloc]initWithCursor:self.fQueryCursor];
} else {
NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"FUD" predicate:predicate];
fQueryOperation = [[CKQueryOperation alloc]initWithQuery:query];
}
fQueryOperation.qualityOfService = NSQualityOfServiceUserInitiated;
__block double fCount = 0;
fQueryOperation.recordFetchedBlock = ^(CKRecord *record) {
fCount = fCount + 1;
[self storeFFromCloudKit:record];
};
fQueryOperation.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *operationError) {
if (operationError) {
NSLog(#"F query error: %#", operationError.localizedDescription);
} else if (cursor) {
NSLog(#"Fetched %.f entires, more coming...",fCount);
self.fQueryCursor = cursor;
[self fetchAllCKEntries];
} else {
NSLog(#"Fetched %.f entries, done",fCount);
self.fQueryCursor = cursor;
}
};
[publicDatabase addOperation:fQueryOperation];
}
Is this really an internal server error or am I missing something here?
I'm working with HealthKit to read steps data from my iOS device.
here is my code:
if ([HKHealthStore isHealthDataAvailable]) {
__block double stepsCount = 0.0;
self.healthStore = [[HKHealthStore alloc] init];
NSSet *stepsType =[NSSet setWithObject:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]];
[self.healthStore requestAuthorizationToShareTypes:nil readTypes:stepsType completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
HKSampleType *sampleType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKSampleQuery *sampleQuery = [[HKSampleQuery alloc] initWithSampleType:sampleType predicate:nil limit:HKObjectQueryNoLimit sortDescriptors:nil resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if (error != nil) {
NSLog(#"results: %lu", (unsigned long)[results count]);
for (HKQuantitySample *result in results) {
stepsCount += [result.quantity doubleValueForUnit:[HKUnit countUnit]];
}
NSLog(#"Steps Count: %f", stepsCount);
} else {
NSLog(#"error:%#", error);
}];
[self.healthStore executeQuery:sampleQuery];
[self.healthStore stopQuery:sampleQuery];
NSLog(#"steps:%f",stepsCount);
}
}];
}
I build and run the code on an iPhone6 which does have steps data and in the Settings -> Privacy -> Health, the app does have been allowed to read data, but the log area only shows:
steps:0.000000
I put a break point on the for-loop and on the NSLog(#"error:%#", error), but the app does not break.
Anybody can help?
Try this code u just change the start date and End date.
-(void) getQuantityResult
{
NSInteger limit = 0;
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:currentDate endDate:[[NSDate date]dateByAddingTimeInterval:60*60*24*3] options:HKQueryOptionStrictStartDate];
NSString *endKey = HKSampleSortIdentifierEndDate;
NSSortDescriptor *endDate = [NSSortDescriptor sortDescriptorWithKey: endKey ascending: NO];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType[HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]
predicate: predicate
limit: limit
sortDescriptors: #[endDate]
resultsHandler:^(HKSampleQuery *query, NSArray* results, NSError *error){
dispatch_async(dispatch_get_main_queue(), ^{
// sends the data using HTTP
int dailyAVG = 0;
for(HKQuantitySample *samples in results)
{
dailyAVG += [[samples quantity] doubleValueForUnit:[HKUnit countUnit]];
}
lblPrint.text = [NSString stringWithFormat:#"%d",dailyAVG];
NSLog(#"%#",lblPrint.text);
NSLog(#"%#",#"Done");
});
}];
[self.healthStore executeQuery:query];
}
Your code stops the query immediately, before it has a chance to run. For this query, there is no reason to call stopQuery: at all unless you want to cancel a query before it finishes. Since the query is not long lived (it doesn't have an updateHandler), it will stop immediately after the resultsHandler is called.
The second problem is that your code attempts to log step count too soon. The query runs asynchronously, and the resultsHandler will be called on a background thread once the query completes. I'd suggest logging stepsCount inside the block.
Finally, if you want to count the user's steps you should us an HKStatisticsQuery instead of summing the results of an HKSampleQuery. HKStatisticsQuery is more efficient and will yield correct results when there are multiple sources of overlapping data in HealthKit. Your current implementation will double count the user's steps if they have both an iPhone and an Apple Watch, for instance.
I had a recordID.recordName (singleID in this example) and that I wanted to query with queryOp; after some searching and experimentation I managed to put this code together... which works, but is it really correct, is there a shorter path perhaps.
CKRecordID *wellKnownIDx = [[CKRecordID alloc] initWithRecordName:singleID];
CKReference *singleREX = [[CKReference alloc] initWithRecordID:wellKnownIDx action:CKReferenceActionDeleteSelf];
CKDatabase *publicDatabase = [[CKContainer containerWithIdentifier:#"iCloud.blah"] publicCloudDatabase];
NSPredicate *predicatex = [NSPredicate predicateWithFormat:#"recordID == %#",singleREX];
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Courses" predicate:predicatex];
CKQueryOperation *queryOp =[[CKQueryOperation alloc] initWithQuery:query];
queryOp.desiredKeys = #[#"record.recordID.recordName"];
queryOp.resultsLimit = 1;
queryOp.recordFetchedBlock = ^(CKRecord *results)
{
NSLog(#"Student Found %#",results.recordID.recordName);
};
queryOp.queryCompletionBlock = ^(CKQueryCursor *cursor, NSError *error)
{
NSLog(#"CKQueryCursor error %#", error);
};
[publicDatabase addOperation:queryOp];
What you have should work, but it looks like all you want to do is to fetch a record with a known recordID. There is no need to use a query for that. It's more code than necessary and will also require a query index on the field.
Instead, use the fetchRecordWithID method on CKDatabase:
https://developer.apple.com/library/ios/documentation/CloudKit/Reference/CKDatabase_class/index.html#//apple_ref/occ/instm/CKDatabase/fetchRecordWithID:completionHandler:
Is there way to run updating of all objects for some entity by one SQL-query?
Not to fetch and run-looping.
For example like to run
UPDATE someEntity SET filed1 = value1 WHERE field2 = value2
Core Data Batch Updates were introduced on iOS 8:
NSBatchUpdateRequest *batchRequest = [NSBatchUpdateRequest batchUpdateRequestWithEntityName: [RSSItem entityName]];
batchRequest.propertiesToUpdate = #{NSStringFromSelector(#selector(read)): #YES};
batchRequest.resultType = NSStatusOnlyResultType; // NSStatusOnlyResultType is the default
batchRequest.affectedStores = #[...]; // Optional, stores this request should be sent to
batchRequest.predicate = [NSPredicate predicateWithFormat:#"..."]; // Optional, same type of predicate you use on NSFetchRequest
NSError *requestError;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.managedObjectContext executeRequest:batchRequest error:&requestError];
if (result) {
// Batch update succeeded
} else {
NSLog(#"Error: %#", [requestError localizedDescription]);
}
However, it does not change the context: it changes the persistent store directly. This means that no validation is done and that you need to update your UI after.
This answer was based on this post by Matthew Morey.
Mercelo's answer will not work for iOS-7 for this you can
NSFetchRequest *fetchAllRSSItems = [NSFetchRequest fetchRequestWithEntityName:[RSSItem entityName]];
NSError *fetchError;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchAllRSSItems error:&fetchError];
if (results == nil) {
NSLog(#"Error: %#", [fetchError localizedDescription]);
} else {
for (RSSItem *rssItem in results) {
rssItem.read = [NSNumber numberWithBool:YES];
}
[self saveManagedObjectContext];
}
I have a stand alone CoreData Entity that contains approximately 30 records. I periodically have to delete the records and repopulate the associated CoreTableViewController. My NSFetchRequest is not returning any records.
But the exact same FetchRequest in the setupFetchedResultsController does return records.
I am sure I am missing something obvious here, but have not figured out what. Any ideas?
- (void)eraseFetchedResults:(NSManagedObjectContext *)myContext
{
NSFetchRequest *allAthletes = [NSFetchRequest fetchRequestWithEntityName:#"ResultsForAthleteSearch"];
allAthletes.sortDescriptors = [NSArray arrayWithObject:[
NSSortDescriptor sortDescriptorWithKey:#"orderRecordsDownloaded"
ascending:YES]];
// no predicate because we want ALL the Athletes
[allAthletes setIncludesPropertyValues:YES]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * athletes = [myContext executeFetchRequest:allAthletes error:&error];
NSLog(#"NSArray athletes is %d", [athletes count]);
//error handling goes here
for (NSManagedObject * athlete in athletes) {
[myContext deleteObject:athlete];
}
NSError *saveError = nil;
[myContext save:&saveError];
}
- (void)setupFetchedResultsController // attaches an NSFetchRequest to this UITableViewController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"ResultsForAthleteSearch"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"orderRecordsDownloaded" ascending:YES]];
// no predicate because we want ALL the Athletes
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.athleteSearchDatabase.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
}
Here is the calling method:
- (void)useDocument
{
NSLog(#"Does athleteSearchDatabase exist? %d", [[NSFileManager defaultManager] fileExistsAtPath:[self.athleteSearchDatabase.fileURL path]]);
NSString *checkName = _searchName;
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.athleteSearchDatabase.fileURL path]]) {
// does not exist on disk, so create it
[self.athleteSearchDatabase saveToURL:self.athleteSearchDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self setupFetchedResultsController];
NSLog(#"checkName is %# before calling fetchAthleteSearchResultsIntoDocument.",checkName);
[self fetchAthleteSearchResultsIntoDocument:self.athleteSearchDatabase whereNameIs:checkName];
}];
} else if (self.athleteSearchDatabase.documentState == UIDocumentStateClosed) {
// exists on disk, but we need to open it
[self eraseFetchedResults:self.athleteSearchDatabase.managedObjectContext];
[self.athleteSearchDatabase openWithCompletionHandler:^(BOOL success) {
[self setupFetchedResultsController];
[self fetchAthleteSearchResultsIntoDocument:self.athleteSearchDatabase whereNameIs:checkName];
}];
} else if (self.athleteSearchDatabase.documentState == UIDocumentStateNormal) {
// already open and ready to use
[self setupFetchedResultsController];
}
}
Well, I did not figure out why this particular fetch request return no records, but I employed
Matthew Frederick's solution to question asked here. Was able to overwrite the records and came away with a more elegant solution.
Will post more details if anyone is interested.