implementing core data to an existing iPhone-project - iphone

I´ve some trouble with implementing Core Data to my existing iPhone-Project. First I wanna give you a more detailed view on it:
Some of my classes are nested into each other: The class "Game" has an NSArray with objects of class "Player", the class "Player" has an NSArray with objects of class "Item" in turn.
What I wanna do is saving an instance of my "uppest" class "Game" (e.g. when leaving my app).
I tried out some tutorials about Core Data, but there are still some questions:
Do I have to create an entity for each of my classes or just for "Game"?
If I have to do it for each one: I think I will have to create ALL relationships between my classes, but: How to create the relationships e.g. between "Game" an "Player" (please remind: I hold MANY players in ONE NSArray)..
What about changing my existing project? What I allready did is copying the missing methods into my AppDelegate. But what will I have to do with my classes, especially with Getter/Setter-methods? Just change "#synthesize" to "#dynamic" in the implementation?
I hope for some light in my dark ;)
Thanks a lot right now
Mac1988

What I recommend is to setup your database model in xcode, then when you have done that... choose the entities and choose from the menu File > New File. Then choose the "Managed Object Class" from the "Cocoa touch class". After "Next" choose where to save the files, and at the next step XCode will ask you which entities should be generated to files.
After you have done that, you can implement the functions you need into your e.g. you delegate. My recommendation is to leave your existing stuff as it is and use the core data classes as their own. Just pull the data you need from you existing classes/arrays and put them in to the database as you need them. When retrieving, the other way around... get them from the DB and add them to your functions / classes.
Example from one of my projects:
The .h file
#class quicklistSet;
#interface rankedAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
[...]
NSMutableArray *_searchHistory;
NSMutableArray *_quickList;
}
[...]
#property (nonatomic, retain) NSMutableArray *_searchHistory;
#property (nonatomic, retain) NSMutableArray *_quickList;
/* Quicklist functions */
- (void)addToQuicklist:(quicklistSet *)theQuicklistSet;
- (BOOL)checkIfQuicklistExists:(quicklistSet*)theQuicklistSet;
- (NSMutableArray *)getQuicklists;
- (void)deleteQuicklist:(NSNumber*)theAppId;
#end
The .m file
#import "quicklistSet.h"
#import "quicklist.h"
#implementation rankedAppDelegate
#synthesize window;
#synthesize tabBarController;
#synthesize _searchHistory, _quickList;
[...]
/* Quicklist functions */
- (void)addToQuicklist:(quicklistSet *)theQuicklistSet
{
BOOL exists = [self checkIfQuicklistExists:theQuicklistSet];
if(!exists)
{
quicklist *theQuicklist = (quicklist *)[NSEntityDescription insertNewObjectForEntityForName:#"quicklist"
inManagedObjectContext:self.managedObjectContext];
[theQuicklist setAppName:[theQuicklistSet _appName]];
[theQuicklist setAppId:[theQuicklistSet _appId]];
[theQuicklist setAppImage:[theQuicklistSet _appImage]];
[theQuicklist setCountryId:[theQuicklistSet _countryId]];
[theQuicklist setCategoryId:[theQuicklistSet _categoryId]];
[theQuicklist setLastCheck:[theQuicklistSet _lastCheck]];
[theQuicklist setLastRank:[theQuicklistSet _lastRank]];
[_quickList addObject:theQuicklist];
[self saveAction];
}
else {
NSLog(#"Existing quicklistSet: %#", [theQuicklistSet _appName]);
}
}
- (BOOL)checkIfQuicklistExists:(quicklistSet*)theQuicklistSet
{
// Get the categories
NSMutableArray *quicklistArray = [self getQuicklists];
BOOL exists = NO;
for(quicklist *dbQuicklist in quicklistArray)
{
if([[dbQuicklist appId] isEqualToNumber:[theQuicklistSet _appId]])
{
exists = YES;
continue;
}
}
return exists;
}
- (NSMutableArray *)getQuicklists
{
if(_quickList == NULL)
{
NSLog(#"Array is null");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"quicklist"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [[self.managedObjectContext
executeFetchRequest:fetchRequest error:&error] retain];
NSMutableArray *returnArray = [[[NSMutableArray alloc] initWithArray:items] retain];
_quickList = returnArray;
[fetchRequest release];
}
else {
NSLog(#"Not null. Count: %d", [_quickList count]);
}
return _quickList;
}
- (void)deleteQuicklist:(NSNumber*)theAppId
{
NSLog(#"Delete row");
// Create a new managed object context for the new book -- set its persistent store coordinator to the same as that from the fetched results controller's context.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"quicklist"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"appId=%#",theAppId];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if([items count] > 0)
{
NSManagedObject *eventToDelete = [items objectAtIndex:0];
[self.managedObjectContext deleteObject:eventToDelete];
[self saveAction];
}
}
/* END Quciklist functions */
[...]
#end
EDIT:
The quicklistSet was my existsing class, the quicklist is my coredata class.

Yes, you want to create an entity for all of the classes you mentioned.
You've already got the answer to this in your question: make a one-to-many relationship. For example, for the players relationship of Game, click the "To-many relationship" checkbox in the data model editor.
You'll want to have your data classes (Game, Player, Item) inherit from NSManagedObject. You'll probably want to remove all of the instance variables that correspond to the attributes you added in Core Data. For the to-many relationships (players, items), you'll definitely want to get rid of the NSArray member variable you were using. Instead, do like you were saying and create #dynamic accessors for the players and items properties. Note that you want to use an NSSet instead of an NSArray for players and items.
For example, the header for your Game class might look like this:
#interface Game : NSManagedObject {
}
#property(nonatomic, retain) NSSet *players
#property(nonatomic, retain) NSString *someOtherProperty;
#property(nonatomic, retain) NSNumber *yetAnotherProperty;
#end
And then your implementation file might look like:
#import "Game.h"
#implementation Game
#dynamic players, someOtherProperty, yetAnotherProperty;
- (void)awakeFromInsert {
// do initialization here
}
// other methods go here
#end
Also, be careful when modifying the players and items properties. The Using Managed Objects section of the Core Data Programming guide has a lot of good details, but basically to add a Player to a Game instance, you would do
[game addPlayerObject:newPlayer];
To actually create the new player, you would do something like:
NSManagedObject *newPlayer = [NSEntityDescription insertNewObjectForEntityForName:#"Player" inManagedObjectContext:context];

Related

Count Distinct items in Core Data

I have a Core Data entity with a property named 'value' which is often repeated. I wish to retrieve only unique values (done) and also how often each one appears, so that I can sort by that property (I'm building an autocomplete function based on existing user input, so knowing how frequently a certain input has appeared is essential).
My fetch request currently looks like this:
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.managedObjectContext];
NSDictionary *entityProperties = [entity propertiesByName];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:10];
[fetchRequest setFetchLimit:20];
[fetchRequest setReturnsDistinctResults:YES];
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:[entityProperties objectForKey:#"value"]]];
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"value" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"value BEGINSWITH[c] %#",predicateString];
[fetchRequest setPredicate:predicate];
return fetchRequest;
I'm a bit stuck with this one. Any ideas?
Thanks in advance!
I don't like this one, but...
You could do an other fetchRequest with a predicate that exactly match the value you are seeking. Allowing duplicate, then have the count of the array.
This one is better, but more work up front.
An other way could be to have a derived property in your data model that keep track of your duplicated count as you create them.
(with that option you could easily sort by duplicated count)
Ok for a Derived property.
First you will need to subclass NSManagedObject and use that subclass in your data model. (in Xcode 3 there was a way to create that quickly, but I don't know that in Xcode 4) But if you name it the same as your entity I think core data will pick it up.
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface Person : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * firstLetterOfName;
#property (nonatomic, retain) NSString * phoneNumber;
#end
And in your implementation you will need to do something like this (there is code snippet in Xcode 4 to get you started, but there is a typo in one of them, unless it have been corrected)
#import "Person.h"
#implementation Person
#dynamic name, phoneNumber, firstLetterOfName;
- (void)setName:(NSString *)value {
[self willChangeValueForKey:#"name"];
[self setPrimitiveValue:value forKey:#"name"];
self.firstLetterOfName = [value substringToIndex:1];
[self didChangeValueForKey:#"name"];
}
#end
You can see that the firstLetterOfName is set each time the Name is set.
You can do the same kind of thing with relationship.
So when you add an item to a relationship you should be able to look up your relationship.
Here is an exemple of something similar, where I need to find if the object I'm adding have the lowest price of it's group because of a derived property call isMeilleurPrixAvecPrixElment. (this is old code, so I don't recall every detail of it, it have been done in OSX.4)
- (void)addPrixHistoriqueObject:(PrixElement_MO *)value
{
NSSet *changedObjects = [[NSSet alloc] initWithObjects:&value count:1];
[self willChangeValueForKey:#"prixHistorique" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
if ([self isPrixRegulierAvecPrixElement:value])
[self enleveL_AutrePrixRegulierPourCommerceDeCePrixElement:value];
if ([self isMeilleurPrixAvecPrixElment:value])
[self echangeMeilleurPrixAvecCePrixElement:value];
[[self primitivePrixHistorique] addObject:value];
[self didChangeValueForKey:#"prixHistorique" withSetMutation:NSKeyValueUnionSetMutation usingObjects:changedObjects];
[changedObjects release];
}
In respond to a comment
depending on your data model and the importance of that aspect in your application, I can think of 3 solutions.
1- Redesing your data model around that aspect.
2- When setting the values query the rest of your entity with a predicate and update a property that have the count.
3- (I'm not sure of that one, but is worth trying) NSManagedObect is an object, so maybe you could have a static dictionary that have the value as a key and the count as value.
I would maybe try number 3 first (it look like the easy one), but I've never done something like that. So I'm not sure for the presitance of a class variable in core data.

How to store array of NSManagedObjects in an NSManagedObject

I am loading my app with a property list of data from a web site. This property list file contains an NSArray of NSDictionaries which itself contains an NSArray of NSDictionaries. Basically, I'm trying to load a tableView of restaurant menu categories each of which contains menu items.
My property list file is fine. I am able to load the file and loop through the nodes structure creating NSEntityDescriptions and am able to save to Core Data. Everything works fine and expectedly except that in my menu category managed object, I have an NSArray of menu items for that category. Later on, when I fetch the categories, the pointers to the menu items in a category is lost and I get all the menu items. Am I suppose to be using predicates or does Core Data keep track of my object graph for me?
Can anyone look at how I am loading Core Data and point out the flaw in my logic? I'm pretty good with either SQL and OOP by themselves, but am a little bewildered by ORM. I thought that I should just be able to use aggregation in my managed objects and that the framework would keep track of the pointers for me, but apparently not.
NSError *error;
NSURL *url = [NSURL URLWithString:#"http://foo.com"];
NSArray *categories = [[NSArray alloc] initWithContentsOfURL:url];
NSMutableArray *menuCategories = [[NSMutableArray alloc] init];
for (int i=0; i<[categories count]; i++){
MenuCategory *menuCategory = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuCategory"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *category = [categories objectAtIndex:i];
menuCategory.name = [category objectForKey:#"name"];
NSArray *items = [category objectForKey:#"items"];
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
for (int j=0; j<[items count]; j++){
MenuItem *menuItem = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuItem"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *item = [items objectAtIndex:j];
menuItem.name = [item objectForKey:#"name"];
menuItem.price = [item objectForKey:#"price"];
menuItem.image = [item objectForKey:#"image"];
menuItem.details = [item objectForKey:#"details"];
[menuItems addObject:menuItem];
}
[menuCategory setValue:menuItems forKey:#"menuItems"];
[menuCategories addObject:menuCategory];
[menuItems release];
}
if (![[self managedObjectContext] save:&error]) {
NSLog(#"An error occurred: %#", [error localizedDescription]);
}
You set a NSArray as to-many relationship object
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
[menuCategory setValue:menuItems forKey:#"menuItems"];
which might cause the trouble.(should throw an exception?) Relationships in CoreData are always unsorted, therefore NSSets. Add a sortIndex property to your entities for ordering.
I had the same issue. There are 2 major problems with using NSSets and Core Data: if you need non-distinct objects and need them ordered. As an example, say you have 2 entities in Core Data: professor and student. The student takes 10 classes for a degree program and you wish to have a (one-to-many) relationship from the student to the professor in order that the classes were taken. Also, the same professor may teach more than one class. This was how I overcame the issue. Create a Binary Data attribute (we'll call it profData) in student and store dictionaries that make it possible to reconstruct the data as needed. Note: don't store an array of professors, since they inherit from NSManagedObject vs. NSObject. That can cause problems. You can bolt on the required methods using a category. In this example, I created a category on Student called ProfList (Student+ProfList.h/m). This keeps the code out of the NSManagedObject subclasses, so if my attributes in Core Data change, I can regenerate the subclasses automatically without wiping out this code. Here is some sample code:
// Student+ProfList.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Student.h"
#import "Professor.h"
#interface Student (ProfList)
- (NSArray *)getStudentsFullList;
- (void)storeStudentsFullList:(NSArray *)fullList;
#end
// Student+ProfList.m
#import "Student+ProfList.h"
#implementation Student (ProfList)
- (NSArray *)getStudentsFullList
{
NSData *storedData = self.profData;
if (!storedData) return nil;
NSMutableArray *fullList = [[NSMutableArray alloc] init];
// Retrieve any existing data
NSArray *arrayOfDictionaries = [NSKeyedUnarchiver unarchiveObjectWithData:storedData];
// Get the full professor list to pull from when recreating object array
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Professor"];
NSSortDescriptor *alphaSort =
[[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:#[alphaSort]];
NSSet *allProfessors = [NSSet setWithArray:[context executeFetchRequest:fetchRequest error:nil]];
for (NSDictionary *dict in arrayOfDictionaries) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name LIKE %#", [dict objectForKey:#"name"]];
NSSet *filteredSet = [allProfessors filteredSetUsingPredicate:predicate];
Professor *newProfessor = [filteredSet anyObject];
newProfessor.index = [dict objectForKey:#"index"];
[fullList addObject:newProfessor];
}
return fullList;
}
- (void)storeStudentsFullList:(NSArray *)fullList
{
NSMutableArray *encodedList = [[NSMutableArray alloc] init];
for (Professor *professor in fullList) {
[encodedList addObject:#{#"index" : #([encodedList count]), #"name" : professor.name}];
}
NSArray *encodedArray = [NSArray arrayWithArray:encodedList];
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:encodedArray];
self.profData = arrayData;
}
#pragma mark - Core Data
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
#end
You store a local variable in a view controller, then send this message to the student instance to get the list and save it locally for use in a table view or whatever.

Core Data - accessing objects returned from fetch EXC_BAD_ACCESS

The following is implemented as a class method in a category on one of my managed object entities, with a bunch of other operations:
+ (NSArray*)newArrayOfAllOtherBibs
{
NSManagedObjectContext* moc = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init];
NSString* entityName = NSStringFromClass([self class]);
NSEntityDescription* entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
NSError* error;
NSArray* items = [moc executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
NSMutableArray* bibArray = [NSMutableArray array];
for(int i = 0; i < [items count]; i++)
{
Person* person = [items objectAtIndex:i];
if(![person.isSelf isEqualToString:#"YES"])
{
// crash here!
if([person.bib length] > 0)
// crash here!
[bibArray addObject:person.bib];
}
}
return [bibArray retain];
}
So it is supposed to look at all entities for Person, returned for a very simple fetch, and add all that are not marked as "self" (attribute isSelf, type NSString) to a new array which is returned to the caller. All the other methods for add/delete/find matching are working well.
Using
NSString* entityName = NSStringFromClass([self class]);
is an attempt to make the function more generic so I can cut&paste these functions for other entities as required with less editing.
Person looks like this:
#interface Person : NSManagedObject
{
}
#property (nonatomic, retain) NSString * bib;
#property (nonatomic, retain) NSString * isSelf;
[...]
#end
Question is, what could explain EXC_BAD_ACCESS on the line marked? Analyzer shows nothing. At that point person is clearly OK or I would expect it to die earlier, on accessing .isSelf. (It could be made a boolean but I find having two kinds of boolean, one BOOL and one NSNumber, error prone). If person.bib is nil then calling length on it should still return 0 right? But it can't be nil anyway since "bib" is the key for these entities and one is never created without it. At this stage nothing has been deleted from the store, it's a fresh store that has been saved since last addition.
The cause of the problem:
NSDictionary *resultDict = [responseString JSONValue];
NSString* bib = [resultDict objectForKey:#"bib"];
person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:moc];
person.bib = bib;
[... lots of stuff setting up relationships for person etc.]
NSError *error;
if (![moc save:&error])
{
NSLog(#"Core Data Save error %#, %#", error, [error userInfo]);
}
The fix:
person.bib = [bib copy];
seems like the bib string was not valid at the time the Person entity was saved.
The best thing to do is set NSZombieEnabled in the executable and debug it from there. This way you'll be able to see what is causing the problem.
If you are loading some vars lazily make sure you set them to nil when you release them because if not next time when accessed them. They will not be "skipped", you app will try to use them but since they are not pointing to a valid object it fails and gives you errors like EXC_BAD_ACCESS
Person.bib may not have been loaded yet.
Did you leave the bib property defined as #dynamic and not #synthesize?
Did you override the bib accessor method? If so your override needs to be KVO friendly.

How to convert an NSArray of NSManagedObjects to NSData

I'm new to Objective C and was wondering if anyone can help me.
I am using core data with a sqlite database to hold simple profile objects which have a name and a score attribute (both of which are of type NSString).
What I want to do is fetch the profiles and store them in an NSData object, please see my code below:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"GamerProfile" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:YES];
NSArray *sortDecriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDecriptors];
[sortDescriptor release];
[sortDecriptors release];
NSError *error;
NSArray *items = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:items];
[self SendData:data];
[fetchRequest release];
When I run the code I'm getting the error "Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[GamerProfile encodeWithCoder:]: unrecognized selector sent to instance 0x3f4b530'"
I presume I have to add an encodeWithCoderClass to my core data NSManagedObject object (GamerProfile) but I'm not sure how to do this even after reading the documentation, My attempt at doing this is below. I'm not sure if I'm going along the right lines with this as get a warning stating "NSManagedObject" may not respond to '-encodeWithCoder'"
I would really, really appreciate it if someone could point me in the right direction!!
Thanks
C
Here is the code for my GamerProfile (CoreData NSManagedObject Object) with my attempt at adding an encodeWithCoder method...
Header File
#import <CoreData/CoreData.h>
#interface GamerProfile : NSManagedObject <NSCoding>
{
}
#property (nonatomic, retain) NSString * GamerScore;
#property (nonatomic, retain) NSString * Name;
- (void)encodeWithCoder:(NSCoder *)coder;
#end
Code File
#import "GamerProfile.h"
#implementation GamerProfile
#dynamic GamerScore;
#dynamic Name;
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:GamerScore forKey:#"GamerScore"];
[coder encodeObject:Name forKey:#"Name"];
}
I got this to work. Here's how.
First create an NSValueTransformer like so:
ArrayToDataTransformer.h
#interface ArrayToDataTransformer : NSValueTransformer {
}
#end
ArrayToDataTransformer.m
import "ArrayToDataTransformer.h"
#implementation ArrayToDataTransformer
+ (BOOL)allowsReverseTransformation {
return YES;
}
+ (Class)transformedValueClass {
return [NSData class];
}
- (id)transformedValue:(id)value {
//Take an NSArray archive to NSData
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:value];
return data;
}
- (id)reverseTransformedValue:(id)value {
//Take NSData unarchive to NSArray
NSArray *array = (NSArray*)[NSKeyedUnarchiver unarchiveObjectWithData:value];
return array;
}
#end
The above is your interface to NSManagedObject, now create one that use it, for example:
Array.h
#import <CoreData/CoreData.h>
#class Arrays;
#interface Array : NSManagedObject
{
}
#property (nonatomic, retain) id myArray;
#property (nonatomic, retain) Arrays * arrayOfArrays;
#end
Array.m
#import "Array.h"
#import "Arrays.h"
#implementation Array
#dynamic myArray;
#dynamic arrayOfArrays;
#end
In the xcdatamodel, Array needs myArray Attributes set as Optional (usually always checked), and Type is: Transformable, and Value Transformer Name: ArrayToDataTransformer
Now you can use it;
NSMutableArray* positionArray;
positionArray = [[NSMutableArray alloc] arrayWithCapacity:[myArray count]];
for(NSArray *pos in myArray) {
[positionArray addObject:[NSString stringWithFormat:#"%#",pos]];
}
NSLog(#"ArrayCtrl : positionArray cnt = %d",[positionArray count]);
//Now add the positionArray to CoreData using the setValue & myArray Key
Array *array = (Array*)[NSEntityDescription insertNewObjectForEntityForName:#"Array" inManagedObjectContext:self.managedObjectContext];
[array setValue:positionArray forKey:#"myArray"];
[myArrays setMyArrays:array];
[self saveAction:array];
[positionArray release];
To retrieve the data from CoreData:
using a one-to-one relationship, thus myArrays points to just one array element
NSArray *positionArray = [myArrays.array valueForKey:#"myArray"];
If you are using a one-to-many, and things are named as above, you'll get back an NSSet.
Core Data should store the Array as a Blob in the database, and a large Array can be written very quickly, say one with 3,500 objects takes less than a second. The performance is comparable to how UIImage is stored and retrieved using pretty much the same concepts. The retrieval I think is even faster.
The alternative is to write each value of the Array individually into Core Data. For this you need to create the appropriate NSManageObject, but beware that you'll have to save 3,500 times for each array value, and for 3,500 items, this will take 20 to 30 seconds.
Thus the above method is great for writing large arrays into CoreData in one shot, and retrieving them also in one shot.
Spent a few hours on this one, was about to give up, and then I saw the light!
NSManagedObject and NSCoding really do not play well together. Consider this answer to a similar question for background and a possible solution.

Setting an attribute on an entity and retrieving it with "one-to-many" relations

I have been through the Apple Developer guides and tutorials and I been through 2 iPhone books brushing on the subject of Core Data.
I am used to handling the "value object"/"entity" side of things and then send them of to a web service or the likes. But on the iPhone I get to handle everything myself… cruel world :)
The Locations, TaggedLocations and PhotoLocations examples from the Apple Developer site do not give me the answers in a way I can "compute". I hope someone here can enlighten me.
I have set up my model using the datamodel GUI. two entities, Person and Dream.
Person has a personName string attribute and a one-to-many dreams relationship.
Dreams has a description string attribute and a one-to-one person relationship.
I have been setting up a simple tableView app.
First view is a list of persons and the second view is a list of their dreams.
This is how I add a person to the modelObjectContext:
Person *newPerson = (Person *)[NSEntityDescription
insertNewObjectForEntityForName:#"Person"
inManagedObjectContext:managedObjectContext];
[newPerson setPersonName:#"Ben Hur"];
OK, I then add a new dream to the context:
Dream *newDream = (Dream *)[NSEntityDescription
insertNewObjectForEntityForName:#"Dream"
inManagedObjectContext:managedObjectContext];
[newDream setDescription:#"I had a nightmare"];
I now add the dream to the person like this:
[newPerson addDreamObject:newDream];
Here it gets a bit hazy to me, because xcode generated different methods/accessors for me on the Person Class:
#class Dream;
#interface Person : NSManagedObject
{
}
#property (nonatomic, retain) NSString * personName;
#property (nonatomic, retain) NSSet* dream;
#end
#interface Person (CoreDataGeneratedAccessors)
- (void)addDreamObject:(Dream *)value;
- (void)removeDreamObject:(Dream *)value;
- (void)addDream:(NSSet *)value;
- (void)removeDream:(NSSet *)value;
#end
In other situations, where I did not have to handle the actual saving, retrieving, data. I would have build an entity/value object called person and given it an Array to store the dreams. But this is not a possible attribute type in core data, and not the way to do it, I have read (in here in similar threads too).
So how does this boilerplate code work?
Am I supposed to use the addDream and send it an NSSet filled with dreams? or can I just trust core data to instantiate this and exclusively use the addDreamObject send the Person entity objects of type Dreams?
I also save the context using the boilerplate code from xcode.
Now I wish to update the view with this person, more precisely his name.
In the cellForRowAtIndexPath method I give it this:
NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [[managedObject valueForKey:#"personName"] description];
Again all is well and the name is displayed on the list.
I set up my DreamViewController to take a Person object as a parameter.
Person *selectedObject = [[self fetchedResultsController] objectAtIndexPath:indexPath];
dreamView.selectedPerson = selectedObject;
Then I push the viewController onto the stack and we enter the DreamView. Here I can not seem to get at the at the dreams related to the person I "sent along" with the view.
This is what I'm trying in the DreamViewController's viewDidLoad method (selectedPerson is the accessor I use to pass the Person object):
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"One Person";
NSManagedObjectContext *context = selectedPerson.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Dream"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
// Handle the error.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
NSMutableArray *mutableArray = [fetchedObjects mutableCopy];
self.dreamArray = mutableArray;
NSLog(#"the length of dreamArray: %i",[self.dreamArray count] );
Dream *d = [dreamArray objectAtIndex:0];
NSLog(#"The Dream object says: %#", [d description]);
[mutableArray release];
[fetchRequest release];
}
I really can't seem to get the hang of this and my current experience with Objective C does not allow me to just grab the "best practice" essence from between the lines of Apple's documentation.
You need to correct a mistake you made in your model, to begin with. You can NOT have an attribute called "description" in your dreams entity: this is forbidden because "description" it's the name of a method.
From the Apple documentation (Core Data programming guide):
Note that a property name cannot be the same as any no-parameter method name of NSObject or NSManagedObject, for example, you cannot give a property the name “description” (see NSPropertyDescription).
The difference between addDreamObject: and addDream: is that the former is used to insert a Dream object in the to-many relationship, while the latter is used to insert or replace one-shot the contexts of the to-many relationship.
You should not use
cell.textLabel.text = [[managedObject valueForKey:#"personName"] description];
you should use simply
cell.textLabel.text = [managedObject valueForKey:#"personName"];
Regarding the dreams related to your person, you do not need an additional fetch request. Once you have your person object, you simply access that person's dreams as follows:
for(Dream *dream in person.dreams){
// process your Dream object
}
Finally, it is not clear why you do not pass explicitly the managed object context to your DreamViewController as an instance variable. This is common practice, also shown in Apple sample codes. Another error is checking for
if (fetchedObjects == nil)
because it is legal to return nil if the query actually found no objects; you must instead check if your NSError object is not nil (you must initialize it to nil BEFORE executing your fetch request):
if(error)
The statement
NSLog(#"The Dream object says: %#", [d description]);
may even crash your application, as explained at the beginning of my answer.