what's the correct way to store an NSURL in Core Data? - ios

I'm trying to archive / unarchive NSManagedObjectIDs in Core Data objects so that next time my app starts up I can retrieve these IDs and use them to fetch specific objects.
I tried "archiving" the ID like this:
//defaultConfiguration is an NSManagedObject defined elsewhere and it works just fine...
// and newObject is also properly initialized, bound to a context,etc...
ArchivedID* newID = [NSEntityDescription insertNewObjectForEntityForName:#"ArchivedID" inManagedObjectContext:managedObjectContext];
[self.defaultConfiguration addArchiveIDObject:newID];
newID.idURI = [[newObject objectID] URIRepresentation];
[managedObjectContext save:&error];
And then unarchiving like this (I'm just going for [anyObject] cause i'm testing and there's only one at this point):
NSManagedObjectID* ID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:[defaultConfiguration.archiveID anyObject]];
But when I try getting the URL back like above, I get the following exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ArchivedID relativeString]: unrecognized selector sent to instance 0x7f59f20'
The attribute in the entity was set up through Xcode to "transformable" and I left the transformer value field in Xcode empty since the Core Data documentation seems to imply if empty it's use the default transformer.
What am I doing wrong?

It's possible you can solve this problem without storing URLs. Consider adding a boolean flag to your model and marking the objects you wish to retrieve as true, then fetch flagged objects next time your app starts.
However, you could try getting the string version of the URL with -absoluteString and storing that.

Ok... It's been 14hrs straight of coding.. I'm an.. eh.. idiot:
I forgot to access the attribute in the ArchivedID object. That is:
NSManagedObjectID* ID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:[defaultConfiguration.archiveID anyObject]];
should be
NSManagedObjectID* ID = [managedObjectContext.persistentStoreCoordinator managedObjectIDForURIRepresentation:[[defaultConfiguration.archiveID anyObject] idURI]];

Hmmm. Seems like there is a much simpler way to do this with CoreData. Remember, CoreData is an object graph. It's good at keeping track of relationships.
Why not just have an entity in CoreData that keeps a one-to-many relationship to the objects you like? Then, you can just insert/remove/search/iterate/whatever over the collection of objects.

I like what #kevboh suggested. You might also consider storing the objectIDs of specific NSManagedObjects in NSUserDefaults. E.g.:
[[NSUserDefaults standardUserDefaults] setObject:featuredNSManagedObjectIDArray
forKey:#"FeaturedNSManagedObjectIDs"];

Related

iPad splitview application with core data

I am new in app development and I have really big issues with my split view core data iPad application. Even though I made sure that I have built all core data structure very well, I have problems when I pass objects between view and saving them via core data. Basically my problem is in this way:
I have peopleviewcontroller where I list all people in list. And I have addpersonviewcontroller where I create new person and save it to coredata. However, even though saving new object in new class seems successful in save-error structure, no data is written to core data. I debuted the code and figured out that managedjectcontext of created person is always null. Hence, it is not written to core data.
I have spent really lots and lots of time for solution, including days of reading stackverflow, but no solution.
Any help about this issue is deeply appreciated. Thabk you in advance.
Your Person is a subclass of NSManagedObject. In your AddPersonViewController, you never created the object. You have to use insert that entity into core data before you save it.
I recommend having some store/repository that handles all of this.
For instance, I have this in my code:
- (NSManagedObject *)createManagedObject:(NSString *)entityName
{
NSManagedObject *objectCreated = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.managedObjectContext];
[self saveContext];
return objectCreated;
}
I then call it with the entity type and it returns a NSManagedObject. You can then cast it to your Person class with
Person *addPerson = (Person *)managedObject;
Now you can make your changes and save it.
A NSManagedObject must be created by the store before it can be saved. You cannot just new it up and expect it to work.

Cannot delete objects in other context

I am facing this problem consistently for over 3 months. I have searched a lot and read related docs and visited many forums but couldn't find working solution. I am getting typical NSManagedObject error while deleting objects. An NSManagedObjectContext cannot delete objects in other contexts.
I tried to go around and tried to delete object using its NSManagedObject ID but to no avail.
NSManagedObjectID *findingsSurveyDataItemApiId = [findingsSurveyDataItemApi objectID];
[self.managedObjectContext deleteObject:[self.managedObjectContext objectWithID:findingsSurveyDataItemApiId]];
Can anyone tell why is above solution still not working?
PS: I have two managed object context in the app.
I guess it might be a misleading error message from Core Data. If the object you want to delete has not yet been saved to the persistent store, objectWithID will not return a valid object, according to the docs:
The data in the persistent store represented by objectID is assumed to exist—if it does not, the returned object throws an exception when you access any property (that is, when the fault is fired).
Use existingObjectWithID:error: instead and check if it returns a non-nil object before trying to delete it.

About the benefit of objectWithID:

The doc says:
If the object is not registered in the context, it may be fetched or
returned as a fault. This method always returns an object. The data in
the persistent store represented by objectID is assumed to exist—if it
does not, the returned object throws an exception when you access any
property (that is, when the fault is fired). The benefit of this
behavior is that it allows you to create and use faults, then create
the underlying data later or in a separate context.
I'm thinking about the last sentence:
The benefit of this behavior is that it allows you to create and use faults, then create the underlying data later or in a separate context.
Does it mean I can use objectWithID: with an arbitrary ID to get a fault handle of an non-existing object first then later create the object with ID? But how can I assign an arbitrary ID to the new object?
In general, Yes you can get a handle to a non existing item an later create that item.
But, since you don't know what ID will be assigned to the item these is not very useful in that case.
You could use obtainPermanentIDsForObjects:error: to obtain the object final ID, but, this is a trip to the store, and will have a performance penalty.
You can use objectWithID: to "warm up" the coordinator cache. in this manner you may fetch objects in the background, and use this method in another context then access these items without hitting the store (much better performance).
Since every NSManagedObjectID must initially come from a fulfilled NSManagedObject and there is no way to create one from scratch, the only possible way to "create the underlying data later" is meaningless, as follows:
NSManagedObjectID *objID = object.objectID;
[moc deleteObject:object];
…
object = [moc objectWithID:objID]; // Deleted so non-existing
[moc insertObject:object]; // Kinda of resurrecting the deleted object, but not really since the data are gone only ID is left. So it is creating a new object with the old ID. But what's the point?
// Fill data into object
…
[moc save:NULL];
If you use -objectWithID:, it will return a fault if the object is not already registered in the managed object context (ie. only if the object hasn't already been fetched and hasn't been faulted in). In the case that it does return a fault, you do not need to do anything to "create the object". Simply accessing the attributes of the object will automatically fire the fault and let you access its data. There is no additional work needed on your part to create additional objects.

Using Core Data in app without UITableView

I have a simple to do list application. It uses dynamically generated text fields spaced programmatically for the tasks (I didn't use UITableView because of some custom animations and whatnot that I want to use).
In the app the user can create multiple lists (home, work, school, etc.) each with their own tasks.
I want to use Core Data to store the information
Saving the information is straightforward enough but updating my Core Data objects (List and Task) are where I'm getting stuck. Also how, with Core Data, to associate in a specific tasks with a specific list.
let me know if I need to clarify anything.
Your best bet is NSFetchedResultsController. You can use it exactly like in the pattern suggested by the Xcode templates (you can look at it by creating a new project Master-Detail and checking "User Core Data").
You can device your object model (entity Task) with a string attribute for the name as well as a NSNumber boolean attribute for done etc. I also recommend a timestamp and maybe a serial number for ordering it (I find NSOrderedSet unreliable). Your entity List should have a to-many relationship to Task. Pretty straight forward.
List <---->> Task
The only difference is now to find the right object, because you cannot use objectAtIndexPath. You can use the index in fetchedResultsController.fetchedObjects for that. Just make sure your objects are ordered as expected.
I'm not totally clear on your question, however, the task of updating a managed object is straightforward. When you're doing an initial add (similar to an "insert" in SQL) you might use code like this:
NSManagedObject *obj;
obj = [NSEntityDescription insertNewObjectForEntityForName:#"UserData" inManagedObjectContext:context];
[obj setValue:user forKey:#"userName"];
[obj setValue:goalCategory forKey:#"goalCategory"];
[obj setValue:goalDetail forKey:#"goalDetail"];
NSError __autoreleasing error;
[context save:&error];
That's about it for inserting a new item. For updating after you're found the managed object you're working on, you just change the values in the managed object and use [context save:&error]. Here's an example:
UserData *uData = (UserData *) managedObj;
uData.itemName = nameText;
NSError __autoreleasing *error;
[context save:&error];
That's pretty much it.
As to the update, once you have selected the object(s) to be updated, they are contained in
fetchedResultsController.fetchedObjects
which is an NSArray. So, you might do something like this:
UserData *uData = (UserData *) [fetchedResultsController.fetchedObjects objectAtIndex:3];
uData.completed = YES;
NSError __autoreleasing *error;
[context save:&error];
So, this would update the field completed in the UserData entity to be == YES for the object at index 3 in the fetchedObjects array.
I know there are other methods of updating and lots of options but I haven't found any need for them. fetchedObjects is an array containing the items returned by your fetch; to update them, cast each object to the entity (which is defined as a NSManagedObject), make the change then context save..
HTH.
First of all, think is it good idea to use Core Data for your project. If your model is light and simple, maybe it will be better to use plists.
If you choose Core Data, just remember 2 rules:
Each thread owns separate NSManagedObjectContext;
Perform operations with context only in its thread.
And don't worry about optimizations now. Realize any scheme of updating your storage. Make sure it works. And then you should try some other update methods.

Core Data many-to-many relationship. Saving an array of strings

I have a GameData entity which is meant to store an array of strings. So I made a 'Value' Entity which has a value string attribute and made a many-to-many relationship between the two entities.
To save the data I use the following code:
//Save values
NSMutableSet* values = [[NSMutableSet alloc] init];
for(NSString* n in gameData.values){
NSManagedObject *val = [NSEntityDescription
insertNewObjectForEntityForName:#"Value"
inManagedObjectContext:context];
[val setValue:n forKey:#"value"];
[values addObject:val];
}
[gd setValue:values forKey:#"values"];
The gameData.values array is currently empty so the code never actually goes into the for loop...but for some reason it crashes at this line [gd setValue:values forKey:#"values"] with the following error.
-[__NSSetM managedObjectContext]: unrecognized selector sent to instance 0x1f0485d0
Where or how am I sending a managedObjectContext selector to my values NSMutableSet??
Maybe you need to check whether the type of your entity is "To Many".
I cannot comment, thats why i am creating an answer.
Why don't you create the subclass for your entities using the xcode and import their header files and use code like below
//Save values
//NSMutableSet* values = [[NSMutableSet alloc] init]; -- No Need of this
for(NSString* n in gameData.values){
Value *val = [NSEntityDescription
insertNewObjectForEntityForName:#"Value"
inManagedObjectContext:context];
[val setValue:n]; // set your string
[val setGame:gd]; // set the game relation here. you can do this, if you have
configured inverse relations. If no create inverse relationship it will be helpful.
}
//[gd setValue:values forKey:#"values"]; you don't need this.
Now just save the context. Everything should be fine. This is much more cleaner than your way. I have never ever used key value for accessing core data entity properties because, it will be confusing and error prone as you have to remember the exact spelling of the property, and it wont throw any error if you have used wrong spelling or wrong key.
i think you should look at core data programming guide
Edit: If your GameEntity stores array of strings then one to many relationship is just enough. You need many to many only if GameEntity has many Strings and Each Strings i.e. Value entity also has many GameEntity. In that case the above code slightly changes.
Instead of
[val setGame:gd];
You need to use
[val addGameObject:gd];

Resources