views:

831

answers:

2

Hi,

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"];



}
+1  A: 

NSManagedObject and NSCoding really do not play well together. Consider this answer to a similar question for background and a possible solution.

Ole Begemann
Thanks Ole that makes sense!
carok
A: 

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!

Pat