iPhone - Create non-persistent entities in core data - cocoa-touch

I would like to use entity objects but not store them... I read that I could create them like this:
myElement = (Element *)[NSEntityDescription insertNewObjectForEntityForName:#"Element" inManagedObjectContext:managedObjectContext];
And right after that remove them:
[managedObjectContext deleteObject:myElement];
then I can use my elements:
myElement.property1 = #"Hello";
This works pretty well even though I think this is probably not the most optimal way to do it...
Then I try to use it in my UITableView... the problem is that the object get released after the initialization. My table becomes empty when I move it!
Thanks
edit: I've also tried to copy the element ([myElement copy]) but I get an error...

Maybe you can try to have two store coordinators in your project. One which have persistence, and the other with no persistence.

couldn't you just do
Element *myElement = [[Element alloc] init];
Then do with it whatever you want, presumably you will add it to an array so it says around for your UITableView.

Consider using transient objects --- that are handled by the managed object context like any other object, but not written to disk on a save operation. They are typically employed to model runtime-only objects, as I suspect you are trying to do.
Here is some info on them:
http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdNSAttributes.html
http://2pi.dk/tech/cocoa/transient_properties.html

One option drawn from an answer to a similar question is initializing the NSManagedObject with a nil context:
Element *myElement = [[Element alloc] initWithEntity:entity
insertIntoManagedObjectContext:nil];
or
Element *myElement = [NSEntityDescription insertNewObjectForEntityForName:#"Element"
inManagedObjectContext:nil];

What I did was using an in-memory store. You can do it as described here: http://commandshift.co.uk/blog/2013/06/06/multiple-persistent-stores-in-core-data/

Related

Design Decision: Adding new field to Core Data model and sorting with it

I'm using NSFetchedResultsController to display a table of my NSManagedObject data.
Up to now, I've been using the name property on my objects to sort them:
NSFetchRequest* request = [[NSFetchRequest alloc] initWithEntityName:[Item entityName]];
NSSortDescriptor* nameSorter = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
request.sortDescriptors = #[nameSorter];
self.frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.moc
sectionNameKeyPath:#"nameInitial"
cacheName:nil];
Note that my sectionNameKeyPath is different from my sort request. As in this answer, I use a transient property on Item, called nameInitial. Its getter just reads name and returns the first letter.
So far so good. But now, I want to add a special condition: if the first word of the name is 'the', I don't want to sort by that. I want to sort by the first letter of the 2nd word. I can't do this with a transient property because now, the NSSortDescriptor on the fetch request will give a different order than the sectionNameKeyPath, which makes NSFetchedResultsController barf.
So I added a nameInitial field to Item and performed a lightweight migration. Now, I can add a NSSortDescriptor using this new attribute. I just have to populate it with the right letter. This is where my problem comes in: What do I do with the objects I already have in the DB, for which the nameInitial attribute is nil? As far as I can tell, these are my options:
Write a code that executes upon the first launch of the new version of the app, reads all the name fields and updates nameInitial appropriately.
Use awakeFromFetch to automatically update nameInitial for each object as it is loaded.
Override the getter of nameInitial, so it updates itself if it's nil.
I don't particularly like any of these options. The first one isn't elegant at all, and the last two mean either the awakeFromFetch or the getter will have unexpected side-effects. Is there a better way to do this that I'm missing?
You shouldn't do any of those. You should be writing a migration which processes this instead of using a lightweight (auto) migration (which can only fill the values in as nil).
Technically, your suggestions will work, but they aren't 'correct', will run slower and will be a maintenance burden in the future.
From your comment, the best option then is your first suggestion. You don't have many choices - use the built in migration processing or write your own version check and migration logic.

MagicalRecord -- A fetch request must have an entity

I have what I believe to be the simplest possible start to using Magical Record. I simply set up the stack and do a findAll call -- which I expect to return an empty array since this is a first run of the application. My code is below. For some reason, what I actually get is
executeFetchRequest:error: A fetch request must have an entity.
I can't for the life of me figure out why. I don't have versions of my data model, or anything really special. Just an entity and a generated NSMangedObject.. Has anyone seen this before?
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[MagicalRecord setupAutoMigratingCoreDataStack];
// Task *task = [Task MR_createEntity];
// task.title = #"Title";
NSArray *contexts = [Task MR_findAll];
NSLog(#"Initial load found %lu contexts", contexts.count);
}
Did you create and populate an NSManagedObjectModel using Xcode and the Core Data Entity Modeler? The error you're seeing happens when the entity isn't found in the model, or you don't have a model in the first place. Double check your entity name and class names as well. If you aren't using mogenerator, the you will need to make sure they match, or map them yourself using MR_entityName in your own entity's code.
Turns out the latest code in the main branch must have a bug.. I pulled the 2.2 branch instead, and it all started working.. Really odd, but there it is for anyone that stumbles on it.. Make sure you pull the latest stable!

Is it possible to know an array (or arrays) which adding an object?

Follow is some code for example.
NSArray *test1 = [[NSArray alloc] initWithObjects:#"TEST", nil];
[someArray addObject:test1];
:
:
too many code lines.
:
:
At some place
NSArray *addingArray = [test1 whoisAddingOrContainingMe(?)];
I want to know a pointer of someArray as method of test1 instance.
Is there a method like this?
No, you can't "reverse lookup" the containers you are contained in.
From a design perspective this would be somewhat difficult, since conceptually there's no difference between having a reference to oneself in an "array", in any other container, or in any other object that's not considered to be a container. Thus, you have to record every single "retain" by passing it an additional "owner" parameter, and since retains and releases can be done in vastly different places you would also need to pass "owner" pointers around so that an eventual "release" can refer to the proper retain.
Or, to put it short: it would be a huge mess :-)
As suggested before, if you know what arrays can actually contain you -- and that should be much easier for your application -- you could check them. Or you could add a list to the objects to record where they have been added, probably via methods like "addTo:" and "removeFrom:".
I think you want NSArray's -containsObject: method.

Comparing NSSets by a single property

I'm trying to determine if two NSSets are "equal" but not in the sense of isEqualToSet. Items in the two sets are the same class but are not the same object, or even references to the same object. They will have one property that is the same though - let's call it 'name'.
Is my best bet in comparing these two sets to do a simple set count test, then a more complex objectsPassingTest: on each item in one set, making sure an item with the same name is in the other set? I'm hoping that something simpler exists to handle this case.
I had the same problem, but I needed to compare multiple properties at the same time (class User with properties Name and Id).
I resolved this by adding a method returning an NSDictionary with the properties needed to the class:
- (NSDictionary *)itemProperties
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:self.name forKey:#"name"];
[dict setObject:self.id forKey:#"id"];
return dict;
}
and then using valueForKey: as Kevin Ballard mentioned:
BOOL userSetsEqual = [[userSet1 valueForKey:#"itemProperties"]
isEqualToSet:[userSet2 valueForKey:#"itemProperties"]];
... where userSet1 and userSet2 were the NSSets that contained User objects.
You could just call valueForKey: on both sets and compare the results.
if ([[set1 valueForKey:#"name"] isEqualToSet:[set2 valueForKey:#"name"]]) {
// the sets match your criteria
}
Looking through the documentation, it seems that there is no way to really handle this special case of yours. You're going to have to write some custom code to handle this. Personally, I would recommend using -sortedArrayUsingDescriptors: and then comparing the arrays, but that's just me. You could also go enumerate through one set, then narrow down the other using -filteredSetUsingPredicate: and get its count.
Whichever method you use, consider the fact that its probably not going to be super efficient. This might be unavoidable, but there are probably ways to go about it that are better than others. Food for thought.

Entity is not key value coding-compliant for the key

if (win) {
// Game was won, set completed in puzzle and time
// Calculate seconds taken
int timeTaken = (int)([NSDate timeIntervalSinceReferenceDate] - self.gameStartTime);
int bestTime = [[self.puzzle valueForKey:#"bestTime"] intValue];
if (timeTaken < bestTime && bestTime != 0) {
[self.puzzle setValue:[NSNumber numberWithInt:timeTaken] forKey:#"bestTime"];
NSLog(#"Best time for %# is %#", [self.puzzle valueForKey:#"name"], [self.puzzle valueForKey:#"bestTime"]);
}
}
This is some code from an iPad game I am making and I am using Core Data for storing the levels. When a level is completed and won, I want to set the best time for that level. The time taken is calculated, and if it is better than the previous best time, I want to set it as the best time for the level.
This code fails on the 'int bestTime' line when it tries to retrieve the best time from self.puzzle which is an NSManagedObject from Core Data. The best time is stored as an Integer 32 in the Core Data model. It fails with a SIGABRT error.
'[<NSManagedObject 0x95334d0> valueForUndefinedKey:]: the entity Puzzle is not key value coding-compliant for the key "bestTime".'
I have searched online for reasons as to why this is happening and how to fix it, but nothing seems to have helped. There are other places where I access Integer values from the Core Data model and they work perfectly, although they are used to filter and sort queries.
I also don't know if the line where I set the value will work.
Any help on this would be greatly appreciated.
EDIT: This is the code that fetches an array of puzzles of which one is taken to be the above puzzle.
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Puzzle" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Set the filter for just the difficulty we want
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"difficulty == %d", difficulty];
[request setPredicate:predicate];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortid" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
Ok, Firstly, I would like to thank everyone who suggested ideas. They may not have helped me solve the problem, but I learnt more about Core Data and it is always good to find out what I should be checking when things don't work.
I don't really know what the problem was. Until this morning I had Xcode open for about 5 days I think and yesterday I added the attribute 'bestTime' to the data model. I can only assume that over the 5 days, Xcode had become a little unstable and thought it was saved when it wasn't. I had checked that I had saved the model attributes, in fact I must have checked 3 or 4 times as well as my habit of hitting Command+S after any change I make.
Anyway, I rebooted my machine earlier today and when I started up Xcode a few minutes ago I realised that 'bestTime' was not in the model file. I added it, reset the settings on the iPad simulator and it worked.
Thank you all again for the help, sorry the solution wasn't more interesting and code based. Although it makes me feel better that my code wasn't the cause.
That managed object doesn't have an attribute named “bestTime”. According to the exception message, it definitely is a Puzzle, so you haven't declared an attribute named bestTime in your model (or you misspelled it or capitalized it differently).
I did solve the same problem by delete and create the data model again and clean then rebuild again.
I think the bug is caused by core data does not update some data inside sometimes.
I don't think there's enough information here to determine the cause. You might try reading the Core Data Troubleshooting Guide; one possible cause could be if you initialized this particular instance of Puzzle using plain init rather than initWithEntity.
If you added attribute bestTime to the model at the later time, you might have forgotten to put declaration and implementation for them in the connected Managed Object Class.
Try convenience actions provided in Design -> Data Model -> Copy Objective-C ... Method Declarations/Implementations to Clipboard (when editing your Model file).
If parsing JSON into a managed object, be sure you're using the coreDataPropertyName property rather than the json-key-name key from JSON. Easy to mix up when they're named so similarly.
This error was driving me nuts, and all because I was using image-url rather than imageURL.