I wonder why my Core Data stop to save changes. In fact, code above worked hours ago. When i try to print out an error, it print (null). Code below is custom method for NSManagedObjectSubclass:
-(void)bindWithModel:(MenuAPIModel*)model{
self.basketId = [model.basketId integerValue];
self.coreId = [model.itemId integerValue];
self.name = [model name];
self.orderId = [model.orderId integerValue];
self.payedFrom = [model payedFrom];
self.persons = [model persons];
self.price = [model.price integerValue];
self.price3 = [model.price3 integerValue];
self.price12 = [model.price12 integerValue];
self.status = [model status];
[[NSManagedObjectContext MR_defaultContext] MR_saveOnlySelfWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) {
NSLog(#"error %#", error.localizedDescription);
}];
Error is null and contextDidSave is YES. But when i try to access entity it prints null, and SQL table is an empty. Why?
I assume that bindWithModel method is in NSManagedObject subclass. If so, then you should use managedObjectContext property from this class rather then MR_defaultContext:
[self.managedObjectContext MR_saveOnlySelfWithCompletion:^(BOOL contextDidSave, NSError * _Nullable error) { (...) }];
Previously it was working probably because context from [NSManagedObjectContext MR_defaultContext] was the same as self.managedObjectContext.
Related
This how NSManagedObject is created
NSEntityDescription *entity = [NSEntityDescription entityForName:strEntityName inManagedObjectContext:managedObjContext];
NSManagedObject * managedObject = (NSManagedObject *)[[NSClassFromString(strEntityName) alloc] initWithEntity:entity insertIntoManagedObjectContext:managedObjContext];
//values are mapped into this object
Now save NSManagedObject to persistent store and fetching currently inserted object like this :
NSError *error;
BOOL isDone = [managedObjectContext save:&error];
//BOOL isDone = [managedObjectContext obtainPermanentIDsForObjects:[NSArray arrayWithObjects:tempManagedObject, nil] error:&error];
if (isDone && error == nil){
//fetch last inserted object here
//make fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:strEntityName];
//make query using fetch request in context
NSError *error;
NSArray *arrFetchRequest = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (arrFetchRequest.count>0){
//This is last inserted object
NSManagedObject *managedObject = [arrFetchRequest lastObject];
return managedObject;
}
}
I have also refered Swift - How to get last insert id in Core Data, store it in NSUserDefault and predicate. But it will not have permanent object ID in NSManagedObject as we are saving temparory ID and fetching using that and we don't have permanent ID at all.
Canyone share any other options?
While inserting NSManagedObject in context, it will have temporaryID (this means it has not been saved to database).
So need to get permanentID (this means it has been saved to database) for NSManagedObject in context, use obtainPermanentIDsForObjects and same NSManagedObject will have permanentID
if (insertManagedObject)
{
NSLog(#"Before %# : %d",insertManagedObject.objectID,insertManagedObject.objectID.isTemporaryID);
//Obtain permanentID for object
NSError *error;
BOOL hasObtainedPermanentID = [managedObjectContext obtainPermanentIDsForObjects:[NSArray arrayWithObjects:insertManagedObject, nil] error:&error]; //;
//check if its done
if (hasObtainedPermanentID && error == nil){
NSLog(#"After %# : %d",insertManagedObject.objectID,insertManagedObject.objectID.isTemporaryID);
//check context has changes and is saved in context
if ([managedObjectContext hasChanges] && [managedObjectContext save:&error])
{
return insertManagedObject;
}
}
}
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'll try to expose my problem, because is a bit complex.
I use Core Data and I have a problem with the data stored.
When I use this code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ItemMessage"];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSMutableArray *values = [[NSMutableArray alloc] init];
if (error == nil) {
for (int i = 0; i<results.count; i++) {
NSLog(#"results %#",[results objectAtIndex:i]);
ItemMessage *itemMessage = [results objectAtIndex:i];
[values addObject:itemMessage];
}
ecc. the problem is that the value printed by NSLog is correct (the "results" contains something) but the itemMessage contains always 0 key/value pairs (it seems empty).
To understand what is the problem I went back and saw that in insertNewObjectForEntityForName I have also this problem, this is the code that I used when I save the messages data in Core Data:
for (id key in objectMessage) {
ItemMessage *itemmessage = [[ItemMessage alloc] init];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newMessage;
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
[newMessage setValue: itemmessage.itemMessageId forKey:#"itemMessageId"];
[newMessage setValue: itemmessage.message forKey:#"message"];
[newMessage setValue: itemmessage.sender forKey:#"sender"];
[newMessage setValue: itemmessage.users forKey:#"users"];
[context save:&error];
if (error != nil) {
NSLog(#"Coredata error");
}
The problem is that newMessage after the insertNewObjectForEntityForName and the setValue contains also 0 key/value pairs.
Can you help me?
You don't seem to insert the new managed objects correctly into the context.
It should be:
for (id key in objectMessage) {
NSManagedObjectContext *context = [appDelegate managedObjectContext];
ItemMessage *itemmessage = (ItemMessage*)[NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage"
inManagedObjectContext:context];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
}
//save your inserts
To create a class file for your managed objects you could:
Go to your model file (xcdatamodeld) ->
select an entity ->
from the menu select:
Editor-> Create NSManagedObjectSubclass -> select the entities your like class files for.
Now you will have managed objects you could access with ease (NSManagedObject subclass) and benefit from CoreData features.
When you insert to manage object contest you have to call save: method, also the saving method should looks something like that:
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
// 2
newMessage.property1 = self.firstNameTextfield.text;
newMessage.property2 = self.lastNameTextfield.text;
if (![context save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}
I have a routine that checks if the last character of a string stored in a Core Data entity is a $ and if so sets a variable 'last' to remember this and then rewrites to Core Data the string with the $ removed.
It does not throw up any errors, and runs through the 'if' routine if the last char is $ but it does not write back to Core Data. Can anyone see what I am doing wrong?
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Observations"];
NSError *error = nil;
observationList = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (int loop1 = 0; loop1 < [observationList count]; loop1++)
{
NSString *classCheckActual = [[observationList objectAtIndex:loop1] valueForKey: #"obsClassName"];
NSString *last = [classCheckActual substringFromIndex:[classCheckActual length] - 1];
NSString *classCheck = #"";
if ([last isEqual: #"$"])
{
classCheck = [classCheckActual substringToIndex:[classCheckActual length] - 1];
NSManagedObject *schoolObject = [[self observationList] objectAtIndex:loop1];
[schoolObject setValue:[NSString stringWithFormat:#"%#", classCheck] forKey:#"obsClassName"];
NSError *error;
[context save:&error];
}
}
The error is that you use two different arrays: observationList in the first part and [self observationList] in the second part.
This is a SyncEngine from an RW tutorial. I need help understanding how only UPDATED records from the web are fetched and processed into Core Data.
- (void)processJSONDataRecordsIntoCoreData {
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
// Iterate over all registered classes --- CHECK!
for (NSString *className in self.registeredClassesToSync) {
if (![self initialSyncComplete]) {
NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
NSArray *records = [JSONDictionary objectForKey:#"results"];
for (NSDictionary *record in records) {
[self newManagedObjectWithClassName:className forRecord:record];
}
} else {
NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:#"objectId"];
if ([downloadedRecords lastObject]) {
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
int currentIndex = 0;
//if dl count is < current index, there is an updated object dl from the web
for (NSDictionary *record in downloadedRecords) {
NSManagedObject *storedManagedObject = nil;
//Quick check to see if they indeed match, if they do, update the stored object with remote service objects
if ([storedRecords count] > currentIndex) {
storedManagedObject = [storedRecords objectAtIndex:currentIndex];
}
//Othwerwise its a new object and you need to create a new NSManagedObject to represent it in CDdb
if ([[storedManagedObject valueForKey:#"objectId"] isEqualToString:[record valueForKey:#"objectId"]]) {
[self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
} else {
[self newManagedObjectWithClassName:className forRecord:record];
}
currentIndex++;
}
}
}
// After all NSMO are created in your context, save it!
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
}
}];
// Cleanup time
[self deleteJSONDataRecordsForClassWithName:className];
[self executeSyncCompletedOperations];
}
[self downloadDataForRegisteredObjects:NO];
}
From what I understand, on the first or initial sync, it fetches JSONDictionaryForClassWithName which reads the downloaded data from disk and creates a newManagedObjectWithClassName.
My confusion is in the update else block. downloadedRecords is populated from JSONDataRecordsForClass which simply calls JSONDictionaryForClassWithName. Then it checks to see if there is at least 1 object in that array. If there is it does this:
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
This fetches all managedObjectsForClass:sortedByKey which is below:
- (NSArray *)managedObjectsForClass:(NSString *)className sortedByKey:(NSString *)key usingArrayOfIds:(NSArray *)idArray inArrayOfIds:(BOOL)inIds {
__block NSArray *results = nil;
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className];
NSPredicate *predicate;
if (inIds) {
predicate = [NSPredicate predicateWithFormat:#"objectId IN %#", idArray];
} else {
predicate = [NSPredicate predicateWithFormat:#"NOT (objectId IN %#)", idArray];
}
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"objectId" ascending:YES]]];
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
}];
return results;
}
The next bit which compares the [storedRecords count] > currentIndex is confusing. Can someone please explain this? I think my confusion lies in what the managedObjectsForClass method does with the usingArraysOfIds & inArrayOfIds.
I would expect that at some point it gets the the updatedAt field from the downloaded records and compares it to the updatedAt field of the CoreData fetched records.
This function is processing the stored JSON. The actual remote fetching and updateAt checking happens in downloadDataForRegisteredObjects and mostRecentUpdatedAtDateForEntityWithName.
[storedRecords count] > currentIndex is a bit crazy. Although in defense of the original programmer, writing any decent syncengine will quickly make you go googoo. Basically he needs to work out which records are existing and which ones are new and update the local data store accordingly, that's all.
I had another look and this code is actually horribly broken. It will only works if either you have the same records both locally and remotely. Or if the new objects have an objectId that sort-wise comes after the last object the local store has. Which is not the case with Parse objectId's.
If you are testing with just one device this works because new objects will be inserted locally before being pushed to the server. Therefor you will always have the same amount of records. If additional records get inserted any other way, this code will do weird things.