I'm writing an application and I am using MagicalRecord as a framework for interacting with Core Data. The application fetches an array of posters from a server and then displays them. Posters can also be created on the app and then uploaded to the server if the user requires it.
So posters created by the user are stored in the local db using Core Data, while posters fetched from the server should only be displayed in the app but not saved locally. How can I use the same Poster class (which now is a subclass of NSManagedObject) to handle both these cases?
Here is my class:
#interface Poster : NSObject
#property (nonatomic, retain) NSNumber * posterID;
#property (nonatomic, retain) NSString * artists;
#end
When I fetch the posters array from the server I allocate a new poster and then assign attributes:
Poster *poster = [[Poster alloc] init];
if ([dict objectForKey:#"id"]) poster.posterID = [dict objectForKey:#"id"];
if ([dict objectForKey:#"artists"]) poster.artists = [dict objectForKey:#"artists"];
But when reaching the linked poster.posterID = [dict etc etc the application crashes with this error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Poster setPosterID:]: unrecognized selector sent to instance 0xaa8b160'
If I create the new object with Poster *poster = [Poster createEntity]; instead of Poster *poster = [[Poster alloc] init];, the app doesn't crash, but when I save the context I find all the posters fetched from the server are saved locally.
How can I solve this?
You cannot just alloc/init a managed object, because a managed object must be associated with a managed object context. poster.posterID = ... crashes because the dynamically created accessor methods do not work without a managed object context. (Correction: As #noa correctly said, you can create objects without a managed object context, as long as you use the designated initializers. But those objects would not be "visible" to any fetch request.)
To create managed objects that should not be saved to disk you can work with two persistent stores: one SQLite store and a separate in-memory store.
I cannot tell you how to do that with MagicalRecord, but with "plain Core Data" it would work like this:
After creating the managed object context and the persistent core coordinator, you assign two persistent stores to the store coordinator:
NSPersistentStore *sqliteStore, *memStore;
sqliteStore = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
if (sqliteStore == nil) {
// ...
}
memStore = [coordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error];
if (memStore == nil) {
// ...
}
Later, when you insert new objects to the context, you associate the new object either with the SQLite store or the in-memory store:
Poster *poster = [NSEntityDescription insertNewObjectForEntityForName:#"Poster" inManagedObjectContext:context];
[context assignObject:poster toPersistentStore:memStore];
// or: [context assignObject:poster toPersistentStore:sqliteStore];
poster.posterID = ...;
poster.artists = ...;
Only the objects assigned to the SQLite store are saved to disk. Objects assigned to the in-memory store will be gone if you restart the application. I think that objects that are not assigned explicitly to a store are automatically assigned to the first store, which would be the SQLite store in this case.
I haven't worked with MagicalRecord yet, but I see that there are methods MR_addInMemoryStore and MR_addSqliteStoreNamed, which would be the appropriate methods for this configuration.
You could also try using the designated initializer -initWithEntity:insertIntoManagedObjectContext: with nil for the second parameter. (In my experience, some aspects of managed objects work fine without a context; others do not.)
There's a bit of further explanation in this answer.
Related
I have this application that is using core data and an NSArrayController to manage some objects in a table. I have the code below to pick up some objects on a directory. My questions is about the section below labeled "Handle Files". I create a new Video object using the url, I copy the metadata attributes using a custom function I wrote. The object is now inserted in the managedObjectContext. My question is, since I have my NSArrayController bound to my managedObjectContext, why do I have to still do [self addObject:newVideo] to have the object shown on my table? Is there a way to force the array controller to pull the object from the managedObjectContext without having to manually add it? It will be a hassle having to be updating both things every time I add or remove an object.
for (NSURL *url in _dirEnumerator) {
NSNumber *_isDirectory = nil;
[url getResourceValue:&_isDirectory forKey:NSURLIsDirectoryKey error:NULL];
if (![_isDirectory boolValue]) {
if (([_mediaTypes containsObject:[[url pathExtension]uppercaseString]])) {
// Handle the files
Video *newVideo = [NSEntityDescription insertNewObjectForEntityForName:#"Video" inManagedObjectContext:_managedObjectContext];
[newVideo copyAttributesFrom:url];
[self addObject:newVideo];
NSLog(#"Inserting video: %#",[newVideo valueForKey:#"name"]);
}
}
}
Well, I had my bindings all wrong an the array controller was not feeding my table correctly. You cannot sneak objects behind the array controller, if you implement the array controller you must let him do his job and that includes adding and removing objects. He will take care of letting the tableview know when things have changed.
I have around 10000 objects of entity 'Message'. When I add a new 'Message' i want to first see whether it exists - and if it does just update it's data, but if it doesn't to create it.
Right now the "find-or-create" algorithm works with by saving all of the Message objects 'objectID' in one array and then filtering through them and getting the messages with existingObjectWithID:error:
This works fine but in my case when I fetch an 'Message' using existingObjectWithID: and then try to set and save a property by setting the property of the 'Message' object and calling save: on it's context it doesn't saves it properly. Has anyone come across a problem like this?
Is there a more efficient way to make find-or-create algorithm?
First, Message is a "bad" name for a CoreData entity as apple use it internally and it cause problems later in development.
You can read a little more about it HERE
I've noticed that all suggested solutions here use an array or a fetch request.
You might want to consider a dictionary based solution ...
In a single threaded/context application this is accomplished without too much of a burden by adding to cache (dictionary) the newly inserted objects (of type Message) and pre-populating the cache with existing object ids and keys mapping.
Consider this interface:
#interface UniquenessEnforcer : NSObject
#property (readonly,nonatomic,strong) NSPersistentStoreCoordinator* coordinator;
#property (readonly,nonatomic,strong) NSEntityDescription* entity;
#property (readonly,nonatomic,strong) NSString* keyProperty;
#property (nonatomic,readonly,strong) NSError* error;
- (instancetype) initWithEntity:(NSEntityDescription *)entity
keyProperty:(NSString*)keyProperty
coordinator:(NSPersistentStoreCoordinator*)coordinator;
- (NSArray*) existingObjectIDsForKeys:(NSArray*)keys;
- (void) unregisterKeys:(NSArray*)keys;
- (void) registerObjects:(NSArray*)objects;//objects must have permanent objectIDs
- (NSArray*) findOrCreate:(NSArray*)keys
context:(NSManagedObjectContext*)context
error:(NSError* __autoreleasing*)error;
#end
flow:
1) on application start, allocate a "uniqueness enforcer" and populate your cache:
//private method of uniqueness enforcer
- (void) populateCache
{
NSManagedObjectContext* context = [[NSManagedObjectContext alloc] init];
context.persistentStoreCoordinator = self.coordinator;
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:self.entity.name];
[r setResultType:NSDictionaryResultType];
NSExpressionDescription* objectIdDesc = [NSExpressionDescription new];
objectIdDesc.name = #"objectID";
objectIdDesc.expression = [NSExpression expressionForEvaluatedObject];
objectIdDesc.expressionResultType = NSObjectIDAttributeType;
r.propertiesToFetch = #[self.keyProperty,objectIdDesc];
NSError* error = nil;
NSArray* results = [context executeFetchRequest:r error:&error];
self.error = error;
if (results) {
for (NSDictionary* dict in results) {
_cache[dict[self.keyProperty]] = dict[#"objectID"];
}
} else {
_cache = nil;
}
}
2) when you need to test existence simply use:
- (NSArray*) existingObjectIDsForKeys:(NSArray *)keys
{
return [_cache objectsForKeys:keys notFoundMarker:[NSNull null]];
}
3) when you like to actually get objects and create missing ones:
- (NSArray*) findOrCreate:(NSArray*)keys
context:(NSManagedObjectContext*)context
error:(NSError* __autoreleasing*)error
{
NSMutableArray* fullList = [[NSMutableArray alloc] initWithCapacity:[keys count]];
NSMutableArray* needFetch = [[NSMutableArray alloc] initWithCapacity:[keys count]];
NSManagedObject* object = nil;
for (id<NSCopying> key in keys) {
NSManagedObjectID* oID = _cache[key];
if (oID) {
object = [context objectWithID:oID];
if ([object isFault]) {
[needFetch addObject:oID];
}
} else {
object = [NSEntityDescription insertNewObjectForEntityForName:self.entity.name
inManagedObjectContext:context];
[object setValue:key forKey:self.keyProperty];
}
[fullList addObject:object];
}
if ([needFetch count]) {
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:self.entity.name];
r.predicate = [NSPredicate predicateWithFormat:#"SELF IN %#",needFetch];
if([context executeFetchRequest:r error:error] == nil) {//load the missing faults from store
fullList = nil;
}
}
return fullList;
}
In this implementation you need to keep track of objects deletion/creation yourself.
You can use the register/unregister methods (trivial implementation) for this after a successful save.
You could make this a bit more automatic by hooking into the context "save" notification and updating the cache with relevant changes.
The multi-threaded case is much more complex (same interface but different implementation altogether when taking performance into account).
For instance, you must make your enforcer save new items (to the store) before returning them to the requesting context as they don't have permanent IDs otherwise, and even if you call "obtain permanent IDs" the requesting context might not save eventually.
you will also need to use a dispatch queue of some sort (parallel or serial) to access your cache dictionary.
Some math:
Given:
10K (10*1024) unique key objects
average key length of 256[byte]
objectID length of 128[byte]
we are looking at:
10K*(256+128) =~ 4[MB] of memory
This might be a high estimate, but you should take this into account ...
Ok, many things can go wrong here this is how to:
Create NSManagedObjectContext -> MOC
Create NSFetchRequest with the right entity
Create the NSPredicate and attache it to the fetch request
execute fetch request on newly created context
fetch request will return an array of objects matching the predicate
(you should have only one object in that array if your ids are distinct)
cast first element of an array to NSManagedObject
change its property
save context
The most important thing of all is that you use the same context for fetching and saving, and u must do it in the same thread cause MOC is not thread safe and that is the most common error that people do
Currently you say you maintain an array of `objectID's. When you need to you:
filter through them and get the messages with existingObjectWithID:error:
and after this you need to check if the message you got back:
exists
matches the one you want
This is very inefficient. It is inefficient because you are always fetching objects back from the data store into memory. You are also doing it individually (not batching). This is basically the slowest way you could possibly do it.
Why changes to that object aren't saved properly isn't clear. You should get an error of some kind. But, you should really change your search approach:
Instead of looping and loading, use a single fetch request with a predicate:
NSFetchRequest *request = ...;
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:#"XXX == %#", YYY];
[request setPredicate:filterPredicate];
[request setFetchLimit:1];
where XXX is the name of the attribute in the message to test, and YYY is the value to test it against.
When you execute this fetch on the MOC you should get one or zero responses. If you get zero, create + insert a new message and save the MOC. If you get one, update it and save the MOC.
I have an NSManagedObject ElementA with several attributes that should exist in ElementB through a parent-child relationship. When setting ElementA as the Parent Entity to ElementB, the NSPersistentStoreCoordinator fails. The ManagedObjectModel is correctly being built, and the entities/classes work separate of each other. The only difference between the app failing and compiling is this parent-child relationship. None of the attributes from either entity overlap.
I don't have enough rep yet, so the images are at the following links: ElementA Model, ElementB Model.
As far as troubleshooting goes, I've tried all of the following:
With and without implementing custom classes.
Setting ElementA as abstract (however I need it to not be abstract)
Removing and then adding in the attributes one at a time (including emptying all attributes of both entities)
Resetting Xcode (clean), the simulator (reset all), and restarting my machine.
I've read up on Apple's Docs (Core Data Programming Guide: Managed Object Models) and everything seems to align with their guidelines for Entity Inheritance.
This is the line that fails:
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
What do I seem to be missing here? It's got to be obvious as this does not seem like it should be this hard. Any and all help is appreciated!
Edit for #Rog's Comment
The application fails as soon as the core data model is accessed for the first time at startup. The new images above show that I am trying to set the Parent Entity of ElementB using the Model Editor. The following is the error message I'm receiving:
uncaught exception 'NSInternalInconsistencyException', reason: 'Bad model. For entity 'ElementA' subentity 'ElementB (0x785d790)' is not registered in NSManagedModelModel. Model has a reference to ElementB (0x785e320)'
Not the full code... but this is how I achieved what #Scott BonAmi is talking about when removing the temporary entities. As I'm still using modelByMergingModels:, it figures out the sub entities itself.
NSMutableArray *finalModels = [NSMutableArray arrayWithCapacity:0];
NSMutableArray *updatedEntities = [NSMutableArray arrayWithCapacity:0];
for (NSManagedObjectModel *immutableModel in allModels) {
NSManagedObjectModel *model = [immutableModel mutableCopy];
for (NSEntityDescription *entity in [model entities]) {
if ([[[entity userInfo] objectForKey:#"TempPlaceholder"] boolValue]) {
// Ignore placeholder.
DULog(#"Ignoring: %#", entity.name);
} else {
[updatedEntities addObject:entity];
}
}
[model setEntities:updatedEntities];
[updatedEntities removeAllObjects];
[finalModels addObject:model];
}
NSManagedObjectModel *model = [NSManagedObjectModel modelByMergingModels:finalModels];
Ended up being a logic error with code I used from another SO answer creating the MOM dynamically.
When adding entities to the array during the looping sequence, ElementB (0x785d790) is added as a subentity of ElementA, and then later in the loop 'ElementB (0x785e320)' is added, thus causing different memory locations and throwing an NSInternalInconsistencyException.
Part of my iOS project polls a server for sets of objects, then converts and saves them to Core Data, to then update the UI with the results. The server tasks happens in a collection of NSOperation classes I call 'services' that operate in the background. If NSManagedObject and its ~Context were thread safe, I would have had the services call delegate methods on the main thread like this one:
- (void)service:(NSOperation *)service retrievedObjects:(NSArray *)objects;
Of course you can't pass around NSManagedObjects like this, so this delegate method is doomed. As far as I can see there are two solutions to get to the objects from the main thread. But I like neither of them, so I was hoping the great StackOverflow community could help me come up with a third.
I could perform an NSFetchRequest on the main thread to pull in the newly added or modified objects. The problem is that the Core Data store contains many more of these objects, so I have to add quite some verbosity to communicate the right set of objects. One way would be to add a property to the object like batchID, which I could then pass back to the delegate so it would know what to fetch. But adding data to the store to fix my concurrency limitations feels wrong.
I could also collect the newly added objects' objectID properties, put them in a list and send that list to the delegate method. The unfortunate thing though is that I have to populate the list after I save the context, which means I have to loop over the objects twice in the background before I have the correct list (first time is when parsing the server response). Then I still only have a list of objectIDs, which I have to individually reel in with existingObjectWithID:error: from the NSManagedObjectContext on the main thread. This just seems so cumbersome.
What piece of information am I missing? What's the third solution to bring a set of NSManagedObjects from a background thread to the main thread, without losing thread confinement?
epologee,
While you obviously have a solution you are happy with, let me suggest that you lose some valuable information, whether items are updated, deleted or inserted, with your mechanism. In my code, I just migrate the userInfo dictionary to the new MOC. Here is a general purpose routine to do so:
// Migrate a userInfo dictionary as defined by NSManagedObjectContextDidSaveNotification
// to the receiver context.
- (NSDictionary *) migrateUserInfo: (NSDictionary *) userInfo {
NSMutableDictionary *ui = [NSMutableDictionary dictionaryWithCapacity: userInfo.count];
NSSet * sourceSet = nil;
NSMutableSet *migratedSet = nil;
for (NSString *key in [userInfo allKeys]) {
sourceSet = [userInfo valueForKey: key];
migratedSet = [NSMutableSet setWithCapacity: sourceSet.count];
for (NSManagedObject *mo in sourceSet) {
[migratedSet addObject: [self.moc objectWithID: mo.objectID]];
}
[ui setValue: migratedSet forKey: key];
}
return ui;
} // -migrateUserInfo:
The above routine assumes it is a method of a class which has an #property NSManagedObjectContext *moc.
I hope you find the above useful.
Andrew
There's a section of the Core Data Programming Guide that addresses Concurrency with Core Data. In a nutshell, each thread should have its own managed object context and then use notifications to synchronize the contexts.
After a little experimentation, I decided to go for a slight alteration to my proposed method number 2. While performing background changes on the context, keep a score of the objects you want to delegate back to the main thread, say in an NSMutableArray *objectsOfInterest. We eventually want to get to the objectID keys of all the objects in this array, but because the objectID value changes when you save a context, we first have to perform that [context save:&error]. Right after the save, use the arrayFromObjectsAtKey: method from the NSArray category below to generate a list of objectID instances, like so:
NSArray *objectIDs = [objectsOfInterest arrayFromObjectsAtKey:#"objectID"];
That array you can pass back safely to the main thread via the delegate (do make sure your main thread context is updated with mergeChangesFromContextDidSaveNotification by listening to the NSManagedObjectContextDidSaveNotification). When you're ready to reel in the objects of the background operation, use the existingObjectsWithIDs:error: method from the category below to turn the array of objectID's back into a list of working NSManagedObjects.
Any suggestions to improve the conciseness or performance of these methods is appreciated.
#implementation NSArray (Concurrency)
- (NSArray *)arrayFromObjectsAtKey:(NSString *)key {
NSMutableArray *objectsAtKey = [NSMutableArray array];
for (id value in self) {
[objectsAtKey addObject:[value valueForKey:key]];
}
return objectsAtKey;
}
#end
#implementation NSManagedObjectContext (Concurrency)
- (NSArray *)existingObjectsWithIDs:(NSArray *)objectIDs error:(NSError **)error {
NSMutableArray *entities = [NSMutableArray array];
#try {
for (NSManagedObjectID *objectID in objectIDs) {
// existingObjectWithID might return nil if it can't find the objectID, but if you're not prepared for this,
// don't use this method but write your own.
[entities addObject:[self existingObjectWithID:objectID error:error]];
}
}
#catch (NSException *exception) {
return nil;
}
return entities;
}
#end
I'm relatively new to Objective C. So far everything has been going really well until I hit CoreData. I just can't get it to work! After spending many hours on something that seems to be pretty straightforward, I'm at my wits' end.
PLEASE help me figure out what I have done wrong:
I created a new Windows-Based app and checked 'use Core Data for storage'
In the xcdatamodel, I created an entity named 'RecipeData' with only one attribute 'recipeName' it is a string
in the app delegate, I load an XML file and parse it. When I parse the recipe name, I use the following:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:managedObjectContext];
I get the following error:
terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'RecipeData'
Which leads me to the big 3 questions:
is there anything really obvious that I am doing wrong?
since I checked 'use Core Data for storage,' it seems the following code is injected automatically into the app delegate .h:
#private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
Does this interfere with the code I am using?
I tried creating a new NSManagedObjectContext called *myManagedObjectContext but that did not work.
One other tidbit, when I add the following right above my code:
if (managedObjectContext == nil) {
NSLog(#"NO CONTEXT");
}
The console prints "NO CONTEXT"
I really appreciate any help. Thanks.
Where has managedObjectContext come from? Is it a typo for managedObjectContext_? The project templates create the latter, not the former. Using the code above with the code provided by the standard project templates should produce a syntax error. I'm guessing you've renamed some things?
You seem to be using managedObjectContext as an ivar. It is a property. Inside the class, there is a private managedObjectContext_ ivar which holds the reference to the object context. You shouldn't access this. You should be accessing the managedObjectContext property. When this property is first accessed, its getter method will create the context for you. Since you aren't accessing the property, the getter method isn't called and the context never gets created.
Where you have code like this:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:managedObjectContext];
...you should be using code like this:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:self.managedObjectContext];
Note the self. bit. This means that you are accessing a property on the self object, not accessing an ivar from the object the method is being called on.
Note that reading a property is the same as calling the getter method, so the above can also be written as:
recipeData *dataName = (recipeData *) [NSEntityDescription insertNewObjectForEntityForName:#"RecipeData" inManagedObjectContext:[self managedObjectContext]];