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?
Related
I have a list of Keys [key1, key2, key3] (in my groups object as an NSArray) and I want to find all the values in my DynamoDB Groups table using Objective-C.
Should be a simple task, but I'm encountering an error and I'm a little confused on how I should forming my filterExpression for AWSDynamoDBScanExpression object:
AWSDynamoDBScanExpression *sc = [AWSDynamoDBScanExpression new];
sc.limit = #10;
sc.filterExpression = #"GroupID IN :val";
sc.expressionAttributeValues = #{#":val":groups};
[[dynamoDBObjectMapper scan:[Group class] expression:sc] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"The request failed. Error: [%#]", task.error);
}
if (task.exception) {
NSLog(#"The request failed. Exception: [%#]", task.exception);
}
if (task.result) {
AWSDynamoDBPaginatedOutput *output = task.result;
NSArray *items = output.items;
}
return nil;
}];
Error:
The request failed. Error: [Error Domain=com.amazonaws.AWSDynamoDBErrorDomain Code=0 "(null)" UserInfo={__type=com.amazon.coral.validate#ValidationException, message=Invalid FilterExpression: Syntax error; token: ":val", near: "IN :val"}]
If u want to find all values u should just do following
[[dynamoDBObjectMapper scan:[Group class] [AWSDynamoDBScanExpression new]] continueWithBlock:^id(AWSTask *task) {
//do stuff with (AWSDynamoDBPaginatedOutput obj).items -> should contain object of class Group
return nil;
}];
If u want to add some filter u can configure AWSDynamoDBScanExpression something like
AWSDynamoDBScanExpression *scanExpression = [[AWSDynamoDBScanExpression alloc] init];
scanExpression.expressionAttributeNames = #{
#"#P": [NSString stringWithFormat:#"%#", <propertyNameToFileter>]
};
scanExpression.filterExpression = #"#P = :val";
scanExpression.expressionAttributeValues = #{
#":val" : <filterCriteriaForProperty>
};
and call same scan method.
In both cases u will receive object AWSDynamoDBPaginatedOutput
Printing description of task->_result:
AWSDynamoDBPaginatedOutput: 0x7ff8faf56cc0
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];
}
so I'm doing application form using CoreData
First I'm creating "Shop" with unique name and some properties.
In application you can edit that "Shop", and I'm trying to make validation by "shopName" to avoid a creation another "Shop" with same name.
I'm using this:
-(BOOL)uniqueEntityExistsWithEnityName {
BOOL returnValue = NO;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Shop"];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"shopName = [cd] %#", _shopName.text];
NSSortDescriptor *shop = [[NSSortDescriptor alloc] initWithKey:#"shopName" ascending:YES];
[request setSortDescriptors: #[shop]];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *matches = [self.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"request = %#",predicate);
if (!matches) {
NSLog(#"Error: Couldn't execute fetch request %#", error);
}
else if([matches count] > 1) {
NSString *existShop = [NSString stringWithFormat:#"Could Be Only One %# Shop", _shopName.text];
UIAlertView *exist = [[UIAlertView alloc]initWithTitle:#"Shop Exists in Your Records"
message:existShop
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[exist show];
NSLog(#"Error: Have more than %lu records",
(unsigned long)[matches count]);
returnValue = YES;
}
else {
NSLog(#"%lu object in record", (unsigned long)[matches count]);
[self oldShopDelete];
[self checkShopPhoneNumber];
editShop.shopName = _shopName.text;
editShop.shopPhoneNumber = _shopPhoneNumber.text;
editShop.shopSecondPhoneNumber = _shopSecondPhoneNumber.text;
editShop.shopEmail = _shopEmail.text;
editShop.shopNote = _shopNoteView.text;
[super saveAndDissmiss];
returnValue = YES;
}
return returnValue;
}
With that code you still have opportunity to save one more edited "Shop" with same name.
But the thing is - I can not make [matches count] = 1 after this I'll no chance to edit that Shop
Maybe there are another way to do such validation?
Check for a name clash only when the Name is actually being set for the first time or edited.
You can also pass the current shop into the predicate to ensure AND SELF != %# so there will be no match with an existing shop being edited but with an unchanged name.
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];
}
While working on an iOS app, I am having issue resolving the properties returned from a NSFetchRequest. This is not about setting the resultType or the propertiesToFetch. It is about using the NSArray of NSDictionary instances.
Here is the actual code below, the crash is near the bottom. Thank you!
BTW, the point of this code is to eventually produce a list of section headers based on hair color (that is not under hats) and then produce a list of people, without hats who have that hair color for the cells. I am not sure this is the right approach to do that, but regardless, the question stands. Thanks Again!
//
// CDHairbrained.m
//
#import "CDHairbrained.h"
#import "CDHair.h"
#import "CDPerson.h"
#implementation CDHairbrained
void defaultErrorBlock(NSError*error) {
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
} else {
NSLog(#" %#", [error userInfo]);
}
UIAlertView* av = [[UIAlertView alloc] initWithTitle:#"Booo..." message:#"MangagedObjectContext Error" delegate:nil cancelButtonTitle:#"cry" otherButtonTitles: nil];
[av show];
}
-(void) initializeObjectContext:(NSManagedObjectContext*)moc {
//NSArray<CDHairs>
for (CDHair *hair in [self fetchAllOfEntityName:#"Hair" InManagedObjectContext:moc]) {
[moc deleteObject:hair];
}
for (CDPerson *person in [self fetchAllOfEntityName:#"Person" InManagedObjectContext:moc]) {
[moc deleteObject:person];
}
//NSDictionary{color}
NSDictionary* hairdata = #{#"red": [NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"blond":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"brown":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc],
#"black":[NSEntityDescription insertNewObjectForEntityForName:#"Hair" inManagedObjectContext:moc]
};
for (NSString* color in hairdata.allKeys) {
CDHair* hair = hairdata[color];
hair.color = color;
}
//NSArray<NSDictionary{name,hair,hat}>
NSArray* peopleData = #[
#{#"name":#"Stan",#"hair":hairdata[#"red"], #"hat":#"no"},
#{#"name":#"Lucy",#"hair":hairdata[#"red"], #"hat":#"no"},
#{#"name":#"Fred",#"hair":hairdata[#"black"], #"hat":#"no"},
#{#"name":#"Sherlock",#"hair":hairdata[#"black"], #"hat":#"yes"},
#{#"name":#"Barney",#"hair":hairdata[#"blond"], #"hat":#"yes"},
#{#"name":#"Dennis",#"hair":hairdata[#"blond"], #"hat":#"yes"}
];
for (NSDictionary* personData in peopleData) {
CDPerson* person =[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:moc];
person.name = personData[#"name"];
person.hair = personData[#"hair"];
person.hat = personData[#"hat"];
}
NSError*error;
[moc save:&error];
if(error) defaultErrorBlock(error);
}
-(NSArray*) fetchAllOfEntityName:(NSString*)entityName InManagedObjectContext:(NSManagedObjectContext*) moc {
NSFetchRequest* request = [NSFetchRequest fetchRequestWithEntityName:entityName];
NSError* error;
NSArray* fetchResults = [moc executeFetchRequest:request error:&error];
if (fetchResults) {
return fetchResults;
}
defaultErrorBlock(error);
return nil;
}
-(NSArray*) fetchDistinctProperties:(NSArray*) propertyDescriptors
forEntityName:(NSString*) entityName
Predicate:(NSPredicate*) predicate
SortedBy:(NSArray*) sortDescriptors
InManagedObjectContext:(NSManagedObjectContext*)moc
FailureBlock:(void(^)(NSError*)) failureBlock
{
// The darnedest thing: you can't query disctict against in memory changes.
// CoreData is more trouble than it is worth.
if (moc.hasChanges) {
[NSException raise:#"FetchDistinct not return in memory changes." format:#"%# has unsaved changes.",moc];
}
NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
fetchRequest.returnsDistinctResults = YES;
fetchRequest.propertiesToFetch = propertyDescriptors;
fetchRequest.resultType =NSDictionaryResultType;
fetchRequest.predicate=predicate;
fetchRequest.sortDescriptors = sortDescriptors;
NSError* error;
NSArray* fetchResults = [moc executeFetchRequest:fetchRequest error:&error];
if (fetchResults) {
NSLog(#"Fetched %3lu properties of %#", (unsigned long)fetchResults.count, entityName );
return fetchResults;
}
if (failureBlock)
failureBlock(error);
else
defaultErrorBlock(error);
return nil;
}
-(void) doIt:(NSManagedObjectContext*)moc {
[self initializeObjectContext:moc];
// Get a list of distinct Hair that is not underhats, to be section headers.
// And Get a list of People, with that Hair and without hats to be section cells.
//
// Expecting visibleHair to contain red, black. Not blond (none visible) Not brown, no people w/ brown hair.
// Get a distinct list of hair properties from all people without hats.
// Presume result is NSArray*<NSDictionary*{"hair":CDHair*}>
NSArray* visibleHair = [self fetchDistinctProperties:#[#"hair"]
forEntityName:#"Person"
Predicate:[NSPredicate predicateWithFormat:#"hat=='no'"]
SortedBy:nil
InManagedObjectContext:moc
FailureBlock:nil
];
// Quick Sanity Check for the debugger
NSDictionary* foundProperties = [visibleHair firstObject];
CDHair* aFoundHair = foundProperties[#"hair"];
NSLog(#"%u",aFoundHair.isFault); // <--- is nil
NSLog(#"aFoundHair: %#",aFoundHair);
NSLog(#"aFoundHair: %#",aFoundHair.color); // <------ CRASH!
// 2013-11-06 12:43:19.513 CDTest[2865:70b] -[_NSObjectID_48_0 color]: unrecognized selector sent to instance 0x8ba8670
NSLog(#"aFoundHair: %#",aFoundHair);
// Get a list of people with a particular Hair Color, who don't have hats.
NSSet* peopleWithAFoundHair = aFoundHair.people; // of CDPerson
NSArray* peopleWithAFoundHairSorted=[peopleWithAFoundHair sortedArrayUsingDescriptors:
[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]
]; // of CDPerson
NSArray*peopleWithAFoundVisibleHairSorted = [peopleWithAFoundHairSorted filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings)
{
CDPerson*p=evaluatedObject;
return [p.hat compare:#"no"]==NSOrderedSame;
}]
]; // of CDPerson
CDPerson* aPerson=[peopleWithAFoundVisibleHairSorted firstObject];
NSLog(#"%#",aPerson);
}
#end
The NSDictionaryResultType returns an array of dictionaries with property names and values, not an array of dictionaries with entity names and values.
Thus, not:
[
{ "person" : PersonObject },
{ "person" : OtherPersonObject }
]
but rather
[
{ "name" : "John", "age" : 30 },
{ "name" : "Jane", "age" : 20 }
]
To do what you want, you need to just fetch the CDPerson entity with NSManagedObjectResultsType.
Person *person = fetchedObjects[0];
NSLog (#"%#", person.name);
Note that "Person.name" (with a capital "P") is probably wrong, as it looks like a class method rather than an instance method.
Your fetchDistinctProperties: method needs an array of NSPropertyDescriptions but you are passing an array of NSString.