//set up notifications
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(dataChanged:)
name:NSManagedObjectContextDidSaveNotification
object:context];
//later
- (void)dataChanged:(NSNotification *)notification{
NSDictionary *info = notification.userInfo;
NSSet *insertedObjects = [info objectForKey:NSInsertedObjectsKey];
NSSet *deletedObjects = [info objectForKey:NSDeletedObjectsKey];
NSSet *updatedObjects = [info objectForKey:NSUpdatedObjectsKey];
Is there anyway to determine from the updatedObjects which fields were actually changed?
thanks,
Michael
The following should do the trick, but you will need to use NSManagedObjectContextWillSaveNotification and access your updated objects through the same NSManagedObjectContext used to save the objects.
for(NSManagedObject *obj in updatedObjects){
NSDictionary *changes = [obj changedValues];
// now process the changes as you need
}
See the discussion in the comments.
Related
I have written a code to parse some JSON and save data to database via magical record:
NSMutableArray *resultsArray = [NSMutableArray array];
NSArray *timesArray = JSON[#"results"];
for (NSDictionary *record in timesArray) {
Time *newTime = [Time MR_createEntity];
newTime.distance = record[#"distance"];
newTime.time = record[#"time"];
newTime.date = [[MMXFormatter instance] dateFromString:record[#"date"]];
newTime.createdAt = [[MMXFormatter instance] dateFromString:record[#"createdAt"]];
newTime.updatedAt = [[MMXFormatter instance] dateFromString:record[#"updatedAt"]];
[resultsArray addObject:newTime];
}
[MagicalRecord saveWithBlock:nil];
The above code does not save to persistent store. I haven't used Magical Record in a while, and seems saving is different from what it used to be. How do i save my data now?
If you want to use saveWithBlock, the code should be
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
Time *newTime = [Time MR_createEntityInContext:localContext];
newTime.distance = ...
...
}
another way is just replace saveWithBlock with MR_saveToPersistentStoreAndWait
NSMutableArray *resultsArray = [NSMutableArray array];
NSArray *timesArray = JSON[#"results"];
for (NSDictionary *record in timesArray) {
Time *newTime = [Time MR_createEntity];
newTime.distance = record[#"distance"];
newTime.time = record[#"time"];
newTime.date = [[MMXFormatter instance] dateFromString:record[#"date"]];
newTime.createdAt = [[MMXFormatter instance] dateFromString:record[#"createdAt"]];
newTime.updatedAt = [[MMXFormatter instance] dateFromString:record[#"updatedAt"]];
[resultsArray addObject:newTime];
}
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
For More Understanding about CoreData With MegicalRecord I would recommend you to go through this tutorial
http://code.tutsplus.com/tutorials/easy-core-data-fetching-with-magical-record--mobile-13680
I am using Magical Record for my app so I can make use of their core data stack that should automatically propagate my changes in my worker context (core data thread) to the default context (of the main thread). I have been creating and updating my objects in a core data write queue and everything was working fine.
Then I ran into this issue that Magical Record was able to save my changes on my worker context, but when it tries to save to the default context, it detects no changes and therefore doesn't save.
Where did I do wrong? In the rest of my app, I am creating and updating in pretty much the same way and it works. Please help. Thank you!
Below is the related code:
Where no changes were detected after all these changes:
dispatch_async(CoreDataWriteQueue(), ^{
if (self.person) {
FTPerson *localPerson = [FTPerson fetchWithID:self.person.id];
[localPerson setName:self.nameField.text];
[localPerson trainWithImages:self.addedImages];
} else {
FTPerson *newPerson = [[FTPerson alloc] initWithName:self.nameField.text andInitialTrainingImages:self.addedImages];
FTGroup *localGroup = [FTGroup fetchWithID:self.group.id];
[newPerson addGroup:localGroup];
}
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
});
I have also tried the saveWithBlock method and no luck:
dispatch_async(CoreDataWriteQueue(), ^{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
if (self.person) {
FTPerson *localPerson = [FTPerson fetchWithID:self.person.id];
[localPerson setName:self.nameField.text];
[localPerson trainWithImages:self.addedImages];
} else {
FTPerson *newPerson = [[FTPerson alloc] initWithName:self.nameField.text andInitialTrainingImages:self.addedImages];
FTGroup *localGroup = [FTGroup fetchWithID:self.group.id];
[newPerson addGroup:localGroup];
}
}];
});
And here is where I created the person and group objects:
dispatch_async(CoreDataWriteQueue(), ^{
FTPerson *newPerson = [[FTPerson alloc] initWithName:#"test" andInitialTrainingImages:#[[UIImage imageNamed:#"test.jpg"]]];
});
dispatch_async(CoreDataWriteQueue(), ^{
FTGroup *newGroup = [[FTGroup alloc] init];
[self setGroup:newGroup];
});
Also the init methods:
Person
- (id)initWithName:(NSString *)name andInitialTrainingImages:(NSArray *)images {
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
self = [FTPerson MR_createInContext:context];
self.name = name;
self.id = [[NSUUID UUID] UUIDString];
self.objectIDString = [[self.objectID URIRepresentation] absoluteString];
self.trainingImages = [[NSMutableArray alloc] init];
[context MR_saveToPersistentStoreAndWait];
return self;
}
Group
- (id)init {
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
self = [FTGroup MR_createInContext:context];
self.id = [[NSUUID UUID] UUIDString];
self.didFinishTraining = NO;
self.didFinishProcessing = NO;
self.photosTrained = #(0);
self.lastProcessedDate = [NSDate date];
[context MR_saveToPersistentStoreAndWait];
return self;
}
FIXED
So the problem ended up being unrelated to magical record.
I was adding objects to my core data NSSet incorrectly. Instead of making it a NSMutableSet and do addObject:, I should have:
NSMutableSet *mutablePhotos = [self mutableSetValueForKey:#"photos"];
[mutablePhotos addObject:photo];
So the problem ended up being unrelated to magical record. I was adding objects to my core data NSSet incorrectly. Instead of making it a NSMutableSet and do addObject:, I should have:
NSMutableSet *mutablePhotos = [self mutableSetValueForKey:#"photos"];
[mutablePhotos addObject:photo];
I don't think you should be using your own dispatch queues when dealing with Core Data. The whole point of the -performBlock: methods on NSManagedObjectContext is that Core Data takes care of executing the provided block on the correct queue.
Now to answer your question. First, are you using MagicalRecord 2.x or 3.0? If 2.x, please make sure you use the develop branch, or alternatively grab the latest release (v2.3beta5 at this time). There are a lot of improvements in the latest dev and release branches.
Second, I think it is important when multithreading with Core Data to do two things:
Always turn on concurrency debugging by editing your scheme and setting -com.apple.CoreData.ConcurrencyDebug 1
Always be very explicit as to which context you mean to use. There is a reason that the method -MR_contextForCurrentThread is no longer recommended by the MagicalRecord team. Modify your create methods to take an NSManagedObjectContext as a parameter so there is never a doubt as to what context is creating it.
Try making the following changes:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
if (self.person) {
FTPerson *localPerson = [self.person MR_inContext:localContext];
[localPerson setName:self.nameField.text];
[localPerson trainWithImages:self.addedImages];
} else {
FTPerson *newPerson = [[FTPerson alloc] initWithName:self.nameField.text andInitialTrainingImages:self.addedImages inContext:localContext];
FTGroup *localGroup = [self.group MR_inContext:localContext];
[newPerson addGroup:localGroup];
}
}];
Person:
- (id)initWithName:(NSString *)name andInitialTrainingImages:(NSArray *)images inContext:(NSManagedObjectContext*)context {
self = [FTPerson MR_createInContext:context];
self.name = name;
self.id = [[NSUUID UUID] UUIDString];
self.objectIDString = [[self.objectID URIRepresentation] absoluteString];
self.trainingImages = [[NSMutableArray alloc] init];
[context MR_saveToPersistentStoreAndWait];
return self;
}
For Person, I notice you pass in an array of images but you assign an empty array to the new person entity. Do you mean to do that?
Group:
-(id)initWithContext:(NSManagedObjectContext*)context {
self = [FTGroup MR_createInContext:context];
self.id = [[NSUUID UUID] UUIDString];
self.didFinishTraining = NO;
self.didFinishProcessing = NO;
self.photosTrained = #(0);
self.lastProcessedDate = [NSDate date];
[context MR_saveToPersistentStoreAndWait];
return self;
}
How can I get the NSManagedObjectID of an object directly after saving?
I've tried using the NSNotification NSManagedObjectContextDidSaveNotification and getting the updated/inserted values and getting the object id from one (the only) managed object, but it's giving me "Unrecognized Selector" when I try to grab the object id.
Can I even get the Object Id right after I save?
- (void)handleDidSaveNotification:(NSNotification *)note
{
NSDictionary *dict = [note userInfo];
NSDictionary *updatedDict = [dict valueForKey:#"updated"];
NSLog(#"Notification: %#", dict);
NSLog(#"Updated Info: %#", updatedDict);
NSManagedObject *core = [updatedDict valueForKey:#"entity"];
NSManagedObjectID *objectId = [core objectID];
}
You are trying to set a dictionary (updatedDict) when the returned data is a NSSet.
you might simply need to get it from the set collection it is in ...
NSSet* s = [dict valueForKey:#"updated"];
[s valueForKey:#"objectID"]
This will return a set of NSManagedObjectIDs.
See NSSet on how to access objects.
I'm looking for a way to track the attribute change of an NSManagedObject.
Currently I use a NSNotifactionCenter to see the changes of my managedobjectcontext:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDataModelChange:) name:NSManagedObjectContextObjectsDidChangeNotification object:self.managedObjectContext];
It fires the handleDataModelChange Methode which looks like this:
- (void)handleDataModelChange:(NSNotification *)note
{
NSSet *updatedObjects = [[note userInfo] objectForKey:NSUpdatedObjectsKey];
if (updatedObjects.count > 0) {
for (NSManagedObject *obj in updatedObjects.allObjects) {
NSLog(#"Object updated: %# with values:",obj.entity.name);
NSDictionary *theAttributes = [self getAllAttributesOf:obj];
for (NSString *attributeName in theAttributes) {
NSLog(#"Name: %# : %#",attributeName,[obj valueForKey:attributeName]);
}
}
}
}
This logs the new attributes of the object if it changed. How can I achieve a way to get the old attribute values as well?
From the NSManagedObject Class Reference:
changedValues
Returns a dictionary containing the keys and (new) values of persistent properties that have been changed since last fetching or saving the receiver.
changedValuesForCurrentEvent
Returns a dictionary containing the keys and old values of persistent properties that have changed since the last posting of NSManagedObjectContextObjectsDidChangeNotification.
I have a case where I should insert object into an entity via UIViewController. I have designed my database model (Entity and attributes). I'm adding the entity through a UIViewController. What am I supposed to add in the didFinishLaunchingwithOptions method in appDelegate.m?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch
return YES;
}
And for the TUTViewController (My own view controller - UIViewController) I have used the below code for inserting object.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
NSString *stripped1 = [response stringByReplacingOccurrencesOfString:#"\r" withString:#""];
NSMutableArray *rows = [NSMutableArray arrayWithArray:[stripped1 componentsSeparatedByString:#"\n"]];
NSMutableArray *contentArray = [NSMutableArray arrayWithCapacity:[rows count]];
NSMutableArray *contentArray1 = [NSMutableArray arrayWithCapacity:[rows count]];
NSArray *components;
NSLog(#"count:%d",[rows count]);
for (int i=0;i<[rows count]; i++) {
if(i == 0 || [[rows objectAtIndex:i] isEqualToString:#""]){
continue;
}
components = [[rows objectAtIndex:i] componentsSeparatedByString:#","];
id x = [components objectAtIndex:0] ;
id y = [components objectAtIndex:1];
id z = [components objectAtIndex:2];
[contentArray addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:x,#"X",y,#"Y", nil]];
[contentArray1 addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:x,#"X",z,#"Y", nil]];
[newManagedObject setValue:[x] forKey:#"timeStamp"];
[newManagedObject setValue:[y] forKey:#"beat"];
[newManagedObject setValue:[z] forKey:#"rate"];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
NSLog(#"Contents of Uterus Contraction: %#",contentArray);
NSLog(#"Contents of Heart Beat: %#",contentArray1);
}
}
}
Is there anything that I'm missing? I'm ending up with the error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '+entityForName: could not
locate an NSManagedObjectModel for entity name 'FetalData''
Did you set up the Core Data Stack or a UIManagedDocument object?
If you didn't set up the Managed Object Model this could be the problem. It means you're probably not loading Managed Object Model that defines FetalData entity. See insertnewobjectforentityforname for further info.
I really suggest to create an empty project and let Xcode to create Core Data stuff for you. In this manner you can see how the code works.
Some Notes
Why do you use a NSFetchResultsController?
Move the save call at the end of your method. In this manner you avoid multiple round trips to the disk.
If you want to start using Core Data, I suggest you core-data-on-ios-5-tutorial-getting-started.
Hope it helps.