Core Data not updating a transformable attribute - iphone

I am using a Core Data NSManagedObject (in an iOS app) with an attribute of type "transformable" to store a chunk of data. The data is encoded/decoded using the built-in NSKeyedUnarchiveFromData value transformer. The problem is that I'm having trouble getting the NSManagedObject to update properly after the binary data has changed. Say, for example, the code looks like:
id temp = [myManagedObject myTransformableAttribute];
//.. do something with temp
[myManagedObject setMyTransformableAttribute:temp];
NSError *error;
if(![[myManagedObject managedObjectContext] save:&error]) {
//present error
}
It seems to me that "re-setting" the transformable attribute onto the managed object, and then saving the managed object, should cause the data to be re-encoded via the NSKeyedUnarchiveFromData value transformer. But the encoder never gets called, and the updated data doesn't get saved.
If instead of re-setting the original data back onto the managed object, I create a copy of the modified data and set that onto the managed object, then the changes are recognized. Is it possible that core data is using a cached version of the data? Or is there something else I'm doing wrong here? Thanks...

Seems you need to override Class initialize method in your NSManagedObject entity subclass also for transformer to work (known Core Data bug).
Following code is from Apple's location code sample, it is tested and works:
http://developer.apple.com/library/ios/#samplecode/Locations/Introduction/Intro.html
+ (void)initialize {
if (self == [Event class]) {
UIImageToDataTransformer *transformer = [[UIImageToDataTransformer alloc] init];
[NSValueTransformer setValueTransformer:transformer forName:#"UIImageToDataTransformer"];
}
}

I just ran into this same problem and apparently it seems to be a known bug:
http://lists.apple.com/archives/Cocoa-dev/2009/Dec/msg00979.html

Related

MagicalRecord Auto Saving entities while created even without calling Save

I have created entity with following methods
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
Contact *contact = [Contact MR_createInContext:localContext];
And
Contact *contact = [Contact MR_createEntity];
Above methods are called inside a for loop and values are set. After that, I didn't executed any save method for above entity instance. However, after loop is completed, when I read from coredata,
NSArray *savedContacts = [Contact MR_findAll];
NSLog(#"Saved contacts : %d", [savedContacts count]);
It prints the saved entities count. So, are these entities autosaved? or are there any settings that is causing MagicalRecord to save each entities?
Any sort of information would be very essential. I am using MagicalRecord version 2.2. The issue has been opened here which points to ask question on SO with MagicalRecord tag.
I found the cause of this behavior.
FROM README DOCUMENT OF MAGICAL RECORD : CHANGES TO SAVING
MR_save has been temporarily restored to it's original state of
running synchronously on the current thread, and saving to the
persistent store. However, the MR_save method is marked as
deprecated and will be removed in the next major release of
MagicalRecord (version 3.0). You should use
MR_saveToPersistentStoreAndWait if you want the same behaviour in
future versions of the library.
So if you create a context from MR_contextForCurrentThread, then above rule will apply which causes each object you create to be saved in persistent store even if you don't call the save method. Similarly, the MR_createEntity method uses context from MR_contextForCurrentThread which was causing an object to save it. As stated above the MR_save is deprecated and will be removed in MR version 3.0 which will prevent it from autosaving in future even you create context via MR_contextForCurrentThread.
To create object with auto save off you can use local context from MR_context only as below :
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
Contact *contact = [Contact MR_createInContext:localContext];

Is there a way to instantiate a NSManagedObject without inserting it?

I have a user interface to insert a Transaction. once the user clicks on a plus he gets the screen and i want to instantiate my Core Data NSManagedObject entity let the user work on it. Then when the user clicks on the Save button i will call the save function.
so down to code:
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:self.managedObjectContext];
//even if i dont call save: its going to show up on my table
[self.managedObjectContext save:&error]
P.S i am using an NSFetchedResultsController on that table and I see that the NSFetchedResultsController is inserting a section and an object to the table.
My thought is if there is a way to instantiate the Transaction NSManagedObject i could update it with out saving untill the client choses to.
For what it's worth, Marcus Zarra seems to be promoting the nil context approach, claiming that it's expensive to create a new context. For more details, see this answer to a similar question.
Update
I'm currently using the nil context approach and have encountered something that might be of interest to others. To create a managed object without a context, you use the initWithEntity:insertIntoManagedObjectContext: method of NSManagedObject. According to Apple's documentation for this method:
If context is not nil, this method
invokes [context insertObject:self]
(which causes awakeFromInsert to be
invoked).
The implication here is important. Using a nil context when creating a managed object will prevent insertObject: from being called and therefore prevent awakeFromInsert from being called. Consequently, any object initialization or setting of default property values done in awakeFromInsert will not happen automatically when using a nil context.
Bottom line: When using a managed object without a context, awakeFromInsert will not be called automatically and you may need extra code to compensate.
here is how i worked it out:
On load, where we know we are dealing with a new transaction, i created an out of context one.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Transaction" inManagedObjectContext:self.managedObjectContext];
transaction = (Transaction *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
then when it came to establishing a relation ship i did this:
if( transaction.managedObjectContext == nil){
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Category" inManagedObjectContext:self.managedObjectContext];
Category *category = (Category *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
category.title = ((Category *)obj).title;
transaction.category = category;
[category release];
}
else {
transaction.category = (Category *)obj;
}
and at the end to save:
if (transaction.managedObjectContext == nil) {
[self.managedObjectContext insertObject:transaction.category];
[self.managedObjectContext insertObject:transaction];
}
//NSLog(#"\n saving transaction\n%#", self.transaction);
NSError *error;
if (![self.managedObjectContext save:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
There's a fundamental problem with using a nil MOC: Objects in different MOCs aren't supposed to reference each other — this presumably also applies when one side of a relationship has a nil MOC. What happens if you save? (What happens when another part of your app saves?)
If your object doesn't have relationships, then there are plenty of things you can do (like NSCoding).
You might be able to use -[NSManagedObject isInserted] in NSPredicate (presumably it's YES between inserting and successfully saving). Alternatively, you can use a transient property with the same behaviour (set it to YES in awakeFromInsert and NO in willSave). Both of these may be problematic if a different part of your app saves.
Using a second MOC is how CoreData is "supposed" to be used, though; it handles conflict detection and resolution for you automatically. Of course, you don't want to create a new MOC each time there's a change; it might be vaguely sensible to have one MOC for unsaved changes by the slow "user thread" if you don't mind some parts of the UI seeing unsaved changes in other parts (the overhead of inter-MOC communication is negligible).
You can insert an NSManagedObjectContext with the -[NSManagedObject initWithEntity:insertIntoManagedObjectContext:], passing nil for the managed object context. You must, of course, assign it to a context (using -[NSManageObjectContext insertObject:] before saving. This is, as far as I know, not really the intended pattern in Core Data, however (but see #mzarra's answer here). There are some tricky ordering issues (i.e. making sure the instance gets assigned to a context before it expects to have one, etc.). The more standard pattern is to create a new managed object context and insert your new object into that context. When the user saves, save the context, and handle the NSManagedObjectDidSaveNotification to merge the changes into your 'main' context. If the user cancels the transaction, you just blow away the context and go on with your business.
An NSManagedObject can be created using the nil as the context, but if there other NSManagedObjects it must link to it will result in an error. The way I do it I pass the context into the destination screen and create a NSManagedObject in that screen. Make all the changes link other NSManagedObjects. If the user taps the cancel button I delete the NSManagedObject and save the context. If the user taps the the save button I update the data in the NSManagedObject, save it to the context, and release the screen. In the source screen I update the table with a reload.
Deleting the NSManagedObject in the destination screen gives core data time to update the file. This is usually enough time for you not to see the change in the tableview. In the iPhone Calendar app you have a delay from the time it saves to the time it shows up in the tableview. This could be considered a good thing from a UI stand point that your user will focus on the row that was just added. I hope this helps.
transaction = (Transaction *)[NSEntityDescription insertNewObjectForEntityForName:#"Transaction" inManagedObjectContext:nil];
if the last param is nil, it will return a NSManagedObject without save to db

Core Data not saving changes to Transformable property

I am saving an NSMutableArray in a Transformable property in my Core Data store. I can create the entity properly with data in the NSMutableArray and then load it out of the property, and even make changes. When I go around my app and re-access it, my changes are saved. However when I reload the app, the changes have not saved in the core data store. Other changes to the entity - e.g. changing its title, which is saved as an NSString - are saved even when I quit and re-open the app.
I read in another StackOverflow question that Transformable attributes are not automatically notified of changes, and that I'd have to call "the appropriate setter before saving". However even using the actual setter functions - I have also tried calling didChangeValueForKey - the property is not saved properly. Any good ideas?
You must, as you note, "re-set" a transformable property:
id temp = [myManagedObject myTransformableAttribute];
//.. do something with temp
[myManagedObject setMyTransformableAttribute:temp];
There is no way that Core Data could appropriately monitor an arbitrary transformable object so that it could 'do the right thing' automatically.
Furthermore, you must be certain that you actually save the managed object context after you modify the transformable attribute:
NSError *error;
if(![[myManagedObject managedObjectContext] save:&error]) {
//present error
}
During a single run of the program, unsaved changes will appear visible because the managed object context keeps modified instances in memory. Without saving the context, however, those changes will not be persisted to disk.
I've noticed that you not only need to re-set the property. But you also need another instance. That is why it wasn't working with NSMutableArray
Transformable *copy = [managedObject.transformableProperty copy];
// do stuff
managedObject.transformableProperty = copy;

CoreData could not fulfill a fault for when objects are updated by HTTP service

I think I understand the error message: CoreData could not fulfill a fault, but I am not sure how I should deal with it.
We have an application where we use Core Data to persist data returned from a JSON service. Today I am doing the following.
Fetch local object from persistent store and return to UI
Ask server if the object is updated - when I get the answer, I update the Core Data managed object
Update UI with the updated object
The problem is; even if I do not use multi threads I sometimes gets an error when the HTTP request deletes managed objects that my UI has retained. I tried to fetch the objects with returnsObjectsAsFaults to NO. I thought I then could access all the relations and properties of an managed object even if it was deleted (as long as my UI had retained it).
How should I solve this issue?
I thought I could use separate NSManagedObjectContext for read and write. I have made this test:
MyAuthorMO *authorUpdate = [[MyAuthorMO alloc] init]; // I have made this init insert the object into the updateContext
authorUpdate.firstname = #"Hans";
authorUpdate.lastname = #"Wittenberg";
authorUpdate.email = #"Hans#somedomain.no";
NSManagedObjectContext *updateContext = [[MyCoreManager getInstance] managedObjectContext];
NSError *error = nil;
[updateContext save:&error];
NSManagedObjectContext *readContext = [[MyCoreManager getInstance] readOnlyContext];
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:#"authorByEmail" substitutionVariables:[NSDictionary dictionaryWithObject:#"Hans#somedomain.no" forKey:#"EMAIL"]];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *authors = [readContext executeFetchRequest:fetchRequest error:&error];
MyAuthorMO * readAuthor = [authors objectAtIndex:0];
// Delete the author with update context:
[updateContext deleteObject:authorUpdate];
[updateContext save:&error];
NSLog(#"Author: %# %#, (%#)", readAuthor.firstname, readAuthor.lastname, readAuthor.email);
The log is outputted just fine as long as I use the readContext for the fetch. If I use the updateContext for the fetch, I get an exception. This looks promising, but I am afraid that I will run into problems at a later stage. Sooner or later I will probably try to access a property that is not fetched completely (a fault). How can I achieve the behaviour I am looking for?
You shouldn't retain managed objects that the context has released. Let the context do that for you.
The problem is that managed objects can exist as either faults or actualized objects. When you retain one, you may retain the fault which contains no data. Even if you do retain the actual object, the object may not behave properly once it has been separated from its context.
In order to handle your scenario, you need a context for the UI and then a context for the server. After either context makes changes, you should merge the context to ensure both are properly updated relative to the store.
Your UI should be configured to reflect the state of data model, you shouldn't have parts of the data model dependent on the state of the UI.
I had the same problem in my database because I refer to object which didnt exist (because I remove it with other relationed object). My solution was to set "No Action" in my relationship.

Model instantiation question when using Core Data

I'm slightly confused in one aspect of Core Data. That is, when do I use the rudimentary alloc/init routine vs created an object with core data and saving it into the current managed object context.
I know that's a rather vague question, so let me give you an example.
I have an application I'm currently working on that iterates through all of a user's contact book on the iPhone. From there, I wrote a model class called 'Person'. I used to do something like this in a loop of people:
Person *person = [[Person alloc] initWithWrapper:mywrapper];
mywrapper would contain an NSDictionary with the attributes for person. Later I'd be able to populate the address book in my app with the person objects.
Now I've started rebuilding parts of the app with Core Data. Do I continue using the strategy above to populate my address book? Or do I do something like this instead:
Person *person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:managedObjectContext];
[person setName:name];
[person setDob:dob];
// Commit the change.
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
The problem is, this code gets executed everytime the app gets started. Should I not be using core data as it will populate the storage mechanism with redundant instances of person everytime the app loads? Should I modify my NSManagedObject (Person class) and add my initWithWrapper: method and continue as I normally would there?
Slightly confused, would love clarification.
You should never be initializing Core Data objects outside of a managed object context - there's simply no point. Having some
Person *person = [[[Person alloc] init] autorelease];
does you no good since you can't save the object, manipulate it, or really do anything useful that Core Data provides without the context (and thus model and store coordinator) backing it up.
You should instead only use the alloc-init combo when you are inserting an object into Core Data for the first time; this is what the initWithEntity:insertIntoManagedObjectContext: method is for. And you're right, every time you call that method you are inserting a new object into the Core Data context and therefore store, and you may wind up with duplicate objects if you're not careful.
What I would instead recommend for you, if you're running code on every startup, is to come up with a Core Data query that returns some set of existing Person objects, and only add objects (using the initialization method) that don't already exist in the store. If the object already exists, modify it instead of creating a new one.
The trick is getting something like this to perform properly. You shouldn't do a Core Data fetch for every contact in the iPhone address book; many small fetches like this are very expensive. You could in theory get two NSSets - one of Person objects, and one of contacts - then compare them by some unique key (like a hash of the first and last names of the contact). I leave the optimization to you.
The key point is this: don't use alloc and init on a Core Data object unless you mean to insert that object for the first time into a context. Instead look at your existing objects and modify them if necessary.
Yeah, it's simplest to add the initWithWrapper method to your Person class. It would be something like this:
- (id) initWithWrapper:(NSDictionary *)wrapper {
NSEntityDescription * person = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:someMOC];
if (self = [super initWithEntity:person insertIntoManagedObjectContext:someMOC]) {
//do your wrapperly initialization here
}
return self;
}
The only downside to this is that this method has to know which managedObjectContext it should insert the object into, so you have to figure out a way to provide that.
That being said, I use this pattern myself all the time.