I am using NSURLConnection to fetch XML data from the server. I am parsing the data and showing it in a tableview. It all works as expected. Now, I would like to save downloaded data for an offline use. The idea was to take downloaded NSData, convert it to NSArray and store it either to NSUserDefaults or in a separate file. However, I am having problems converting NSData to NSArray.
I added the logic to (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data method. What I am trying to do is as follows:
NSError *error;
NSPropertyListFormat plistFormat;
id object = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:&plistFormat error:&error];
if (error != nil) {
NSLog(#"Error %#", [error localizedDescription]);
[error release];
}
if ([object isKindOfClass:[NSArray class]]) {
NSLog(#"IS Array");
NSArray *objectArray = object;
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:#"myKey"];
} else {
NSLog(#"Not an array");
}
In log I get as follows:
Error The operation couldn’t be completed. (Cocoa error 3840.)
Not an
array
If I remove error handling and leave just the line
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data];
my application crashes with the following message: `
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '*** -[NSKeyedUnarchiver
initForReadingWithData:]: incomprehensible archive (0x3c, 0x3f, 0x78,
0x6d, 0x6c, 0x20, 0x76, 0x65)'
Why is this happening? What is Cocoa error 3840?
My object implements NSCoding protocol, and has methods encodeWithCoder and initWithCoder. Does every property of my object has to be encoded / decoded?
Edit: Here is my object:
Currency.h
#interface Currency : NSObject<NSCoding>{
CGFloat value;
NSString *code;
NSDate *date;
NSString *description;
NSString *imagePath;
}
#property (nonatomic, assign) CGFloat value;
#property (nonatomic, retain) NSString *code;
#property (nonatomic, retain) NSDate *date;
#property (nonatomic, retain) NSString *description;
#property (nonatomic, retain) NSString *imagePath;
Currency.m
#implementation Currency
#synthesize value;
#synthesize code;
#synthesize date;
#synthesize description;
#synthesize imagePath;
static NSString * const keyCode = #"code";
static NSString * const keyDescription = #"description";
static NSString * const keyValue = #"value";
- (void)dealloc {
[code release];
[date release];
[description release];
[imagePath release];
[super dealloc];
}
- (void)encodeWithCoder:(NSCoder *)coder
{
if ([coder allowsKeyedCoding]) {
[coder encodeObject:code forKey: keyCode];
[coder encodeObject:description forKey: keyDescription];
[coder encodeFloat:value forKey: keyValue];
}
}
}
- (id)initWithCoder:(NSCoder *) coder {
self = [[Currency alloc] init];
if (self != nil)
{
code = [[coder decodeObjectForKey:keyCode] retain];
description = [[coder decodeObjectForKey:keyDescription] retain];
value = [coder decodeFloatForKey:keyValue];
}
return self;
}
#end
Revised answer:
So, there's three different concepts here: Generic XML, propertyLists (in either binary or XML format), and an Archive. You parse XML with an NSXMLParser or other library to convert into Obj-C objects. You can save core Obj-C objects (NOT including your custom class Currency) into property lists and read them back. Or you can archive any object/object graph (using encoders) into an archive and read it back with NSKeyedUnarchiver.
Even though underneath they may share implementations, you can't mix them. For example, first you tried to read XML as a propertyList, and even though pLists are XML, your generic CurrencyData XML doesn't have the right format for a plist (e.g. no ). In addition, even if you were to write a plist out, you'd have to convert Currency to a NSDictionary in order to make it storable in a plist, which means you wouldn't need the encoders.
Then you tried to read XML with NSKeyedUnarchiver and got the message "incomprehensible archive". Also correct, as it wasn't created with NSKeyedArchive.
So you can't use the original stream and directly plist or unarchive into your objects. But to save the parsed XMLArray for offline use, you can either convert Currency into an NSDictionary and use property lists, or just leave it as it is and use NSKeyedArchive and Unarchive like this (which leverages the encoders/decoders you've provided):
//To save
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:xmlArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"myKey"]; //or store in another file
//To later read
NSData * data2 = [[NSUserDefaults standardUserDefaults] valueForKey:#"myKey"];
NSArray * newXMLArray = [NSKeyedUnarchiver unarchiveObjectWithData:data2];
Hope that clears it up. Investigating it also cleared up my own understanding of when to use which technique.
//Original comment:
If you've already parsed the incoming data into a model for your tableView, why not serialize that copy of the data rather than the original stream?
Is there a reason why you don't just save the NSData object directly to a file with
data writeToFile:(NSString*)path atomically:(BOOL)flag
?
I don't think you get an error in your NSPropertyList call - the propertyListWithData function returns nil if an error occurred, and I suppose the error is valid only then. You probably should check on return value != nil, and then print the error. Error 3840 marks the beginning of the range of property list errors, that doesn't look like an error in itself.
The description of propertyListWithData also says that it returns a property list, not an array, so it's not surprising that isKindOfClass says it's not an array ... see here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSPropertyListSerialization_Class/Reference/Reference.html
Related
In one of my methods, I fetched and parsed a JSON and placed it inside an NSArray called jsonArray in -(void)method1. I then copied the contents of that jsonArray to an NSMutableArray called copiedJsonArray to be used on other methods. Problem is, copiedJsonArray crashes whenever I log its contents in the console from the other methods -(void)method2 but it logs fine in -(void)method1.
How can I fix this?
In my header file:
#interface MainViewController : UIViewController
#property (nonatomic, retain) NSMutableArray *copiedJsonArray;
In my implementation file:
#synthesize copiedJsonArray;
- (void)viewDidLoad
{
[self method1];
}
- (void)method1
{
NSString *urlString = [NSString stringWithFormat:THE_URL];
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *jsonString = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSDictionary *jsonDictonary = [jsonString JSONValue];
NSArray *jsonArray = [jsonDictonary valueForKeyPath:#"QUERY.DATA"];
self.copiedJsonArray = [[NSMutableArray alloc] initWithArray:jsonArray copyItems:YES];
NSLog(#"Copied JSON Array in Method 1: %#", self.copiedJsonArray);
[self method2];
}
- (void)method2
{
NSLog(#"Copied JSON Array in Method 2: %#", self.copiedJsonArray);
}
I also tried doing this too but it does the same error:
copiedJsonArray = [jsonArray mutableCopy];
I also tried implementing NSCopy but fails too:
#interface MainViewController : UIViewController <NSCopying>
{
NSMutableArray *copiedJsonArray;
}
I'm doing this so that I can do a loop in my copiedJsonArray without fetching its contents from JSON again and again when the user taps on my UISegmentedControl.
If you call method2 before method1 it will crash as copiedJasonArray has not been created. You should not create instance variables inside methods (as you cannot know if they have been called). You should do it when you create your viewController, in viewDidLoad for example.
And use properties:
#interface
#property (retain) NSMutableArray* copiedJsonArray;
#end
then either
#synthesize copiedJsonArray = _copiedJsonArray
or leave that line it out (the compiler will put it in automatically in 4.5)
access as self.copiedJsonArray or _copiedJSONArray.
Outside of getters,setters,inits and deallocs, use the self. form, it's safer.
You could also create _copiedJsonArray lazily in the setter:
- (NSMutableArray*) copiedJsonArray
{
if (!_copiedJasonArray)
_copiedJsonArray = [NSMutableArray alloc] init;
return _copiedJasonArray;
}
I have a relativley simple app which persists data to a plist file located in the documents folder. The data loads into a UITableView at startup. The user can then edit, delete or add records and any changes get saved back to the plist file.
Now I would like to share this data (the plist file) across devices using iCloud. I have looked at the documentation and my understanding is that I need to create a UIDocument to "manage" the plist file.
I have looked at several iCloud tutorials however they all store a simple string within a property in the UIDocument class, not an entire file (like a plist).
How do I share my plist file (or any other file, for that matter) to iCloud using the UIDocument object?
Would I convert the plist file contents to NSData, then save that in a property in the UIDocument? Should I be using use NsFileWrapper instead?
I seem to be having a difficult time wrapping my head around the UIDocument/iCloud arrangement. I am probably making this more complicated then it really is.
Not sure if anybody still needs a solution for that but I found a nice way to get this to work.
Since UIDocument only accepts Data as NSData or NSFilewrapper, I first created a Category for the NSDictionary Class that returns a NSDictionary from NSData. Here's the two files for the Category:
NSDictionary+DictFromData.h:
#import <Foundation/Foundation.h>
#interface NSDictionary (DictFromData)
+ (id)dictionaryWithData:(NSData *)data;
- (id)initWithData:(NSData *)data;
#end
and the NSDictionary+DictFromData.m
#import "NSDictionary+DictFromData.h"
#implementation NSDictionary (DictFromData)
+ (id)dictionaryWithData:(NSData *)data {
return [[[NSDictionary alloc] initWithData:data] autorelease];
}
- (id)initWithData:(NSData *)data {
NSString *tmp = nil;
self = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:data
mutabilityOption:NSPropertyListImmutable
format:NULL
errorDescription:&tmp];
NSAssert1(tmp == nil,#"Error in plist: %#",tmp);
return [self retain];
}
#end
(source)
If you now import this Category in your UIDocument Subclass, you can easily load and save your Plist File to your iCloud container.
To load your Plist from iCloud add this to your UIDocument subclass (The Property contents is an NSDictionary):
- (BOOL)loadFromContents:(id)contents
ofType:(NSString *)
typeName error:(NSError **)outError {
if ([contents length] > 0){
self.contents = [NSDictionary dictionaryWithData:contents];
} else {
self.contents = nil;
}
// call some Methods to handle the incoming NSDictionary
// maybe overwrite the old Plist file with the new NSDictionary
return YES;
}
For saving your Data back to the iCloud add this:
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError {
NSData * plistData = [[[NSData alloc]initWithContentsOfFile:YOUR_PLIST_FILE]autorelease];
return plistData;
}
If you now call:
[myUIDocument updateChangeCount:UIDocumentChangeDone];
YOUR_PLIST_FILE is getting synchronized. Remember that it takes about 10-15sec for your iCloud Container to update.
To use a plist with UIDocument, you can subclass UIDocument and override the following 2 methods with self.myDictionary (your plist) declared as a NSMutableDictionary.
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError
{
if ([contents length] > 0)
{
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:(NSData *)contents];
NSMutableDictionary *dataDictionary = [unarchiver decodeObjectForKey:#"data"];
self.myDictionary = dataDictionary;
[unarchiver finishDecoding];
[unarchiver release];
}
else
{
self.myDictionary = [NSMutableDictionary dictionary];
}
return YES;
}
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
NSMutableData *data = [[[NSMutableData alloc] init] autorelease];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
if( !self.myDictionary )
{
self.myDictionary = [NSMutableDictionary dictionary];
}
[archiver encodeObject:self.myDictionary forKey:#"data"];
[archiver finishEncoding];
[archiver release];
return data;
}
This has been asked before and people have given very good instructions on how to do this, e.g. here.
However, I was wondering if I really need to work with NSCoder if I simply wanted to save one NSMutableArray (containing various instances of another NSMutableArray) to a file? I tried this but only got an error message:
-(void)saveLibraryDat {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"myLibrary.dat"];
NSError *error;
[myLibrary writeToFile:filePath atomically:YES];
if (error) {
NSLog(#"There was an error saving myLibrary.dat: %#", error);
}
}
My error message:
2011-05-13 22:00:47.840 MoleNotes[15437:207] There was an error saving myLibrary.dat: (
1,
2
)
So I guess I have to work with NSCoder, right? If so, I was wondering how to go about this. People have explained how to do this with a class, but in my case, I have a NSMutableArray (myLibrary) which contains various instances of a class. Will I have to implement the NSCoder in this class and the NSMutableArray?
I alloc my library like this:
myLibrary = [[NSMutableArray alloc] init];
And then add instances of a class called NoteBook.m like this:
NoteBook *newNoteBook = [[NoteBook alloc] init];
newNoteBook.titleName = #"Notes"; // etc.
[myLibrary addObject:newNoteBook];
So where exactly do I put the NSCoder commands? Only into my NoteBook.m class? Will this automatically take care of myLibrary?
Thanks for any suggestions.
EDIT:
So I've updated my code, but I guess the big problem is that my NSMutableArray myLibrary contains several instances of a custom class I've set up (called notebook). I have set up NSCoding for this class (and all its variables) so that I can save it and load it.
Now my app works totally fine if I create the NSMutableArray in the app (i.e. when the app is started for the very first time, no file exists), instead of loading it from disk:
-(void) setupLibrary {
myLibrary = [[NSMutableArray alloc] init];
NoteBook *newNoteBook = [[NoteBook alloc] init];
newNoteBook.titleName = #"Notes";
/...
If I load it from disk, it works fine as well:
-(void)loadLibraryDat {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"myLibrary.dat"];
myLibrary = [[NSMutableArray alloc] init];
myLibrary = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
if (!myLibrary) {
// if it couldn't be loaded from disk create a new one
NSLog(#"myLibrary.dat empty... set up new one");
[self setupLibrary];
} else { NSLog(#"Loading myLibrary.dat successful."); }
}
If I log everything which is contained in my library after loading it, everything is still fine. E.g. the following works totally fine:
NSLog(#"%#", [[self.myLibrary objectAtIndex:0] titleName]);
The big problem is, however, if any other method tries to access myLibrary. For instance, if I call the very same log command from another method, the app will crash and I get this error message:
[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510
2011-05-14 14:09:10.490 Notes[17091:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510'
This sounds to me as if myLibrary has become deallocated somehow, but I can't see why. How could this have happened? I have the feeling that I did something wrong in my NSCoding set up... because if I simply create myLibrary in code, everything works like wonderfully. It's only if I load it from the disk, that the app will crash.
Here is the class setup:
#import <Foundation/Foundation.h>
#interface NoteBook : NSObject <NSCoding> {
NSString *titleName;
NSString *fileName;
NSMutableArray *tabTitles;
NSMutableArray *tabColours;
NSMutableArray *tabReference;
}
#property (nonatomic, retain) NSString *titleName;
#property (nonatomic, retain) NSString *fileName;
#property (nonatomic, retain) NSMutableArray *tabTitles;
#property (nonatomic, retain) NSMutableArray *tabColours;
#property (nonatomic, retain) NSMutableArray *tabReference;
-(id)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;
#end
//
// NoteBook.m
#import "NoteBook.h"
#implementation NoteBook
#synthesize titleName, fileName, tabTitles, tabColours, tabReference;
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.titleName = [aDecoder decodeObjectForKey:#"titleName"];
self.fileName = [aDecoder decodeObjectForKey:#"fileName"];
self.tabTitles = [aDecoder decodeObjectForKey:#"tabTitles"];
self.tabColours = [aDecoder decodeObjectForKey:#"tabColours"];
self.tabReference = [aDecoder decodeObjectForKey:#"tabReference"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:titleName forKey:#"titleName"];
[aCoder encodeObject:fileName forKey:#"fileName"];
[aCoder encodeObject:tabTitles forKey:#"tabTitles"];
[aCoder encodeObject:tabColours forKey:#"tabColours"];
[aCoder encodeObject:tabReference forKey:#"tabReference"];
}
#end
EDIT:
I think I've solved it... I forgot a little 'self'... which messed it all up and deallocated myLibrary:
self.myLibrary = [NSKeyedUnarchiver
unarchiveObjectWithFile:filePath];
if (self.myLibrary == nil) {
NSLog(#"myLibrary.dat empty... set up new one");
[self setupLibrary];
} else { NSLog(#"Loading myLibrary.dat successful."); }
Your code is busted. The "error" variable is uninitialized and never set, so when you check it, you're just seeing random garbage data. If you want to know whether the write was successful, check the return value of writeToFile:atomically:. It will be YES if the write succeeded and NO if it didn't.
However, NSArray's writeTo… methods are for creating plists. If non-property-list objects are in your array, that method isn't appropriate, and an archiver is what you want. Just do something like [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:writeToFile:filePath atomically:YES].
To make your objects conform to NSCoding correctly, just have them implement initWithCoder: and encodeWithCoder:, and in those methods, use NSCoder's storage methods to store the object's instance variables (and the retrieval methods to get them back out).
NSCoder is a protocol that your class must conform to in order to be archived to data/file. Works something like Serealizabe in Java.
Add conformance to the class header like this:
#interface NoteBook : NSObject <NSCoder> { // …
And then you must implement two methods:
-(id)initWithCoder:(NSCoder)decoder;
{
self = [super initWithCoder:decoder];
if (self) {
_someIvar = [decoder decodeObjectForKey:#"someKey"];
// And more init as needed…
}
return self;
}
-(void)encodeWithCoder:(NSCoder)coder;
{
[super encodeWithCoder:coder];
[coder encodeObject:_someIvar forKey#"someKey"];
/// Etc…
}
I would also advice against using -[NSArray writeToFile:atomically:] since in work with property list compliant objects only, not coding compliant classes. The property list object are NSString, NSData, NSArray, or NSDictionary, NSDate, and NSNumber. The list can not be extended.
Instead use NSKeyedArchiver/NSKeyedUnarchiver. Almost as simple to use:
if (![NSKeyedArchive archiveRootObject:yourArrat toFile:path]) {
// It failed.
}
I am getting a strange EXC_BAD_ACCESS error when running my app on iOS4. The app has been pretty solid on OS3.x for some time - not even seeing crash logs in this area of the code (or many at all) in the wild.
I've tracked the error down to this code:
main class:
- (void) sendPost:(PostRequest*)request {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURLResponse* response;
NSError* error;
NSData *serverReply = [NSURLConnection sendSynchronousRequest:request.request returningResponse:&response error:&error];
ServerResponse* serverResponse=[[ServerResponse alloc] initWithResponse:response error:error data:serverReply];
[request.objectToNotifyWhenDone performSelectorOnMainThread:request.targetToNotifyWhenDone withObject:serverResponse waitUntilDone:YES];
[pool drain];
}
(Note: sendPost is run on a separate thread for each invocation of it. PostRequest is just a class to encapsulate a request and a selector to notify when complete)
ServerResponse.m:
#synthesize response;
#synthesize replyString;
#synthesize error;
#synthesize plist;
- (ServerResponse*) initWithResponse:(NSURLResponse*)resp error:(NSError*)err data:(NSData*)serverReply {
self.response=resp;
self.error=err;
self.plist=nil;
self.replyString=nil;
if (serverReply) {
self.replyString = [[[NSString alloc] initWithBytes:[serverReply bytes] length:[serverReply length] encoding: NSASCIIStringEncoding] autorelease];
NSPropertyListFormat format;
NSString *errorStr;
plist = [NSPropertyListSerialization propertyListFromData:serverReply mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&errorStr];
}
return self;
}
ServerResponse.h:
#property (nonatomic, retain) NSURLResponse* response;
#property (nonatomic, retain) NSString* replyString;
#property (nonatomic, retain) NSError* error;
#property (nonatomic, retain) NSDictionary* plist;
- (ServerResponse*) initWithResponse:(NSURLResponse*)response error:(NSError*)error data:(NSData*)serverReply;
This reliably crashes with a bad access in the line:
self.error=err;
...i.e. in the synthesized property setter!
I'm stumped as to why this should be, given the code worked on the previous OS and hasn't changed since (even the binary compiled with the previous SDK crashes the same way, but not on OS3.0) - and given it is a simple property method.
Any ideas? Could the NSError implementation have changed between releases or am I missing something obvious?
The setter calls [retain] on the new value, and [release] on the old value. One of those must be invalid (and non-nil) to cause the bad access.
sendPost doesn't initialize it's local error variable and if it is not set by NSURLConnection then it will contain garbage. Try initializing error to nil in sendPost.
Do you ever free serverResponse in the sendPost: message?
You init never calls its parent init. Try something like:
- (ServerResponse*) initWithResponse:(NSURLResponse*)resp error:(NSError*)err data:(NSData*)serverReply
{
if (self = [super init])
{
// ....
}
return self;
}
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.