Saving an NSMutableArray filled with custom objects - ios

How do I save/read an NSMutableArray filled with objects I made a class for?
This is what I have so far... (where 'ObjectA' represents my class, and 'objects' is an array containing many instances of 'ObjectA')
//Creating a file
//1) Search for the app's documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//2) Create the full file path by appending the desired file name
NSString *documentFileName = [documentsDirectory stringByAppendingPathComponent:#"save.dat"];
//Load the array
objects = [[NSMutableArray alloc] initWithContentsOfFile: _documentFileName];
if(objects == nil)
{
//Array file didn't exist... create a new one
objects = [[NSMutableArray alloc]init];
NSLog(#"Did not find saved list, Created new list.");
}
else
{
NSLog(#"Found saved list, Loading list.");
}
This will load an array if it exists. It works if the array is filled with property type objects like NSNumbers, I tested that. If I fill it with my own objects, it crashes! This is the error: (where 'objectAInt' is a private int belonging to 'ObjectA')
-[__NSCFNumber objectAInt]: unrecognized selector sent to instance 0x15d417f02013-11-19 17:28:58.645 My Project[1791:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber objectAInt]: unrecognized selector sent to instance 0x15d417f0'
What do I need to do to make my class, 'ObjectA', work with the save/read process like it does with NSNumbers and other NS objects of type (id)?
Thank You!
P.S. My class implements and is NSCoding compliant. If more information is needed please don't hesitate to ask! :)
EDIT -- Here is my ObjectA (per request) (it is a subclass of NSObject)
//
// ObjectA.m
// My Project
//
// Created by Will Battel on 8/8/13.
//
//
#import "ObjectA.h"
#implementation ObjectA
#synthesize objectAString, objectAString2, objectAString3, objectAInt;
-(id)initWithName:(NSString *)_objectAString{
self = [super init];
objectAString = _objectAString;
objectAInt = 2;
objectAString3 = #"$0.00";
return self;
}
-(id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
NSLog(#"Decoding");
[self setObjectAString:[decoder decodeObjectForKey:#"objectAStringKey"]];
[self setObjectAString2:[decoder decodeObjectForKey:#"objectAString2Key"]];
[self setObjectAString3Price:[decoder decodeObjectForKey:#"objectAString3Key"]];
[self setobjectAInt:[decoder decodeIntForKey:#"objectAIntKey"]];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
NSLog(#"Encoding");
[encoder encodeObject:objectAString forKey:#"objectAStringKey"];
[encoder encodeObject:objectAString2 forKey:#"objectAString2Key"];
[encoder encodeObject:objectAString3 forKey:#"objectAString3Key"];
[encoder encodeInt:objectAInt forKey:#"objectAIntKey"];
}
#end

Since the array contains NSCoding compliant objects, you can use the coding methods, like this...
- (NSMutableArray *)instancesFromArchive {
// compute documentFileName using your original code
if ([[NSFileManager defaultManager] fileExistsAtPath:documentFileName]) {
return [NSKeyedUnarchiver unarchiveObjectWithFile:documentFileName];
} else {
return [NSMutableArray array];
}
}
// archives my array of objects
- (BOOL)archiveInstances {
// compute documentFileName using your original code
return [NSKeyedArchiver archiveRootObject:self.objects toFile:documentFileName];
}

NSNumber class is NSCoding compliant. Are your own objects NSCoding compliant?

Related

How can I automatically initialize mutable arrays/ dictionaries for my PFObject subclass? (Parse.com framework)

I have the following code in various parts of my app:
MyPFObjectSubclass *instance = [MyPFObjectSubclass object];
instance.myMutableArray = [NSMutableArray array];
instance.myMutableDictionary = [NSMutableDictionary array];
I am constantly forgetting to initialize these types, and running into problems later when I attempt setValue:forKey or addObject methods.
It's a nice-to-have, I admit, but I would like to play it safe and roll these initializations into +object if possible.
My PFObject subclasses all look roughly like this. I call [MyPFObject tlObject] to create a new instance.
#implementation MyPFObject
#dynamic objectUUID;
// Other fields
+ (MyPFObject*) tlObject
{
MyPFObject* obj = [self object];
[obj tlObjectInit];
// Other initializations
return obj;
}
+ (NSString*) parseClassName
{
return #"MyPFObject";
}
+ (PFQuery*) query
{
PFQuery* query = [PFQuery queryWithClassName: [self parseClassName]];
// Add includeKeys here
return query;
}
#end
I have a category on PFObject that includes tlObjectInit. The field objectUUID is there so that I have a value that can uniquely identify the object BEFORE IT IS SAVED. This is necessary because I sometimes create sets of objects that refer to one another. The Parse objectID is not set until it is saved.
#implementation PFObject (TL)
- (void) tlObjectInit
{
NSString* format = [[self parseClassName] stringByAppendingString: #"-%#"];
[self setObject: [NSUUID uuidStringInFormat: format]
forKey: #"objectUUID"];
}
// Add other initializations here
#end
In your MyPFObjectSubclass, override the init method:
-(instancetype) init
{
if (self = [super init])
{
_myMutableArray = [NSMutableArray array];
_myMutableDictionary = [NSMutableDictionary dictionary];
}
return self;
}
I guess your object method call one way or the other the init method.
Edit:
It looks like you use the Parse framework. As told in the reference, the PFObject init method shouldn't be overridden by subclasses.

How to Unarchive data and print? App crashing

I have a save method that saves info entered from a ViewController. I put that object with the saved info into an array, and I want to archive that array to a file. The app works fine until unarchive and try to NSLog the info. The app then crashes. Can anyone help me figure out why?
- (IBAction)Save:(UIButton *)sender {
self.homeworkAssignment = [[Homework alloc] init];
self.homeworkAssignment.className = self.ClassNameField.text;
self.homeworkAssignment.assignmentTitle = self.AssignmentTitleField.text;
self.homeworkAssignment.assignmentDiscription = self.DiscriptionTextView.text;
self.homeworkAssignment.pickerDate = self.DatePicker.date;
NSMutableArray *MyHomeworkArray = [[NSMutableArray alloc] init];
[MyHomeworkArray addObject:self.homeworkAssignment];
//Create file
NSString *filePath = [self dataFilePath];
//Archive my object
[NSKeyedArchiver archiveRootObject:MyHomeworkArray toFile:filePath];
//Unarchive my object to check
Homework *archivedHomework = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"%# %#", archivedHomework.className, archivedHomework.assignmentTitle);
//^^^^^If I comment this line out, the app does not crash^^^^^^^^^
And this is the method that creates the file
- (NSString*)dataFilePath
{
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docDir stringByAppendingPathComponent:#"MyHomework.data"];
NSFileHandle *file = [NSFileHandle fileHandleForWritingAtPath:filePath];
if (!file) {
NSLog(#"Attempting to create the file");
if (![[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]) {
NSLog(#"Failed to create file");
}
else
file = [NSFileHandle fileHandleForWritingAtPath:filePath];
}
return filePath;
}
Here is the output error info:
2013-10-09 20:22:03.524 HW1ARC[902:11303] -[__NSArrayM assignmentTitle]: unrecognized selector sent to instance 0x71c72a0
2013-10-09 20:22:03.524 HW1ARC[902:11303] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM assignmentTitle]: unrecognized selector sent to instance 0x71c72a0'
* First throw call stack:
(0x1c96012 0x10d3e7e 0x1d214bd 0x1c85bbc 0x1c8594e 0x28df 0x10e7705 0x1b2c0 0x1b258 0xdc021 0xdc57f 0xdb6e8 0x2df1d3 0x1c5eafe 0x1c5ea3d 0x1c3c7c2 0x1c3bf44 0x1c3be1b 0x1bf07e3 0x1bf0668 0x17ffc 0x1dbd 0x1ce5 0x1)
libc++abi.dylib: terminate called throwing an exception
(lldb)
EDIT :
Homework.h
#interface Homework : NSObject
<
NSCoding
>
#property (nonatomic, strong) NSString *className;
#property (nonatomic, strong) NSString *assignmentTitle;
#property (nonatomic, strong) NSString *assignmentDiscription;
#property (nonatomic, strong) NSDate *pickerDate;
#property (nonatomic, strong) UISwitch *Switch;
#end
Homework.m
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.className forKey:#"className"];
[aCoder encodeObject:self.assignmentTitle forKey:#"assignmentTitle"];
[aCoder encodeObject:self.assignmentDiscription forKey:#"assignmentDiscription"];
[aCoder encodeObject:self.pickerDate forKey:#"pickerDate"];
[aCoder encodeObject:self.Switch forKey:#"Switch"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
self.className = [aDecoder decodeObjectForKey:#"className"];
self.assignmentDiscription = [aDecoder decodeObjectForKey:#"assignmentDiscription"];
self.assignmentTitle = [aDecoder decodeObjectForKey:#"assignmentTitle"];
self.pickerDate = [aDecoder decodeObjectForKey:#"pickerDate"];
self.Switch = [aDecoder decodeObjectForKey:#"Switch"];
}
return self;
}
#end
Look at what you are doing. You create a Homework object, add it to an array, then archive the array.
But when you unarchive the data, you assign the unarchived object (which is an array) to a Homework variable. You then attempt to use the array like a Homework object. This is why the error is coming from __NSArrayM (the internal representation of a mutable array).
This code:
//Unarchive my object to check
Homework *archivedHomework = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"%# %#", archivedHomework.className, archivedHomework.assignmentTitle);
should be:
//Unarchive my object to check
NSMutableArray *homeworkArray = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
Homework *archivedHomework = homeworkArray[0];
NSLog(#"%# %#", archivedHomework.className, archivedHomework.assignmentTitle);
Show the header of the Homework class, as well as it's implementations of initWithCoder and encodeWithCoder.
(In order for what you are trying to do to work, the Homework class must conform to NSCoding, which means it has to implement those 2 methods.)
Your method dataFilePath doesn't really make sense. It creates a path for a file in the documents directory, which is fine. It also creates an NSFileHandle as a local variable and then doesn't do anything with that file handle. There is no reason to create a file handle. It is not doing you any good, and might prevent your file from writing at all. Just build a path string and then use archiveRootObject: toFile: on the array, like you are doing.
The error you report, unrecognized selector assignmentTitle sent to an NSArray object sounds like you have a zombie (an object that is being deallocated and then has a message sent to it after it's memory is used for another object.)
Is your project ARC? Zombies are unusual with ARC, although still not impossible.

iOS Terminating app due to uncaught exception 'NSUnknownKeyException'

I am pretty new to objective c and iOS programming, and I have this pretty strange error. The app in question initializes a NSMutableArray with a preset set of values of a custom type I made using NSObject. Which is manipulated by the app. If new values are added during app run time, they are saved using NSUserDefaults, and are brought up from NSUserDefaults along with the default values on next app open.
This is the error I get:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFConstantString 0xb404> valueForUndefinedKey:]: this class is not key value coding-compliant for the key score.'
*** First throw call stack:
(0x1c9b012 0x10d8e7e 0x1d23fb1 0xb84d1d 0xaf100b 0xaf0fbd 0xb0f247 0xb3023c 0xb30056 0x3e40 0x3c5f 0x11f5ad 0x10ec705 0x202c0 0x20258 0x242ff4 0x10ec705 0x202c0 0x20258 0xe1021 0xe157f 0xe1056 0x246af9 0x10ec705 0x202c0 0x20258 0xe1021 0xe157f 0xe06e8 0x4fcef 0x4ff02 0x2dd4a 0x1f698 0x1bf6df9 0x1bf6ad0 0x1c10bf5 0x1c10962 0x1c41bb6 0x1c40f44 0x1c40e1b 0x1bf57e3 0x1bf5668 0x1cffc 0x290d 0x2835)
libc++abi.dylib: terminate called throwing an exception
I am not quite sure what the error is or how to go about debugging it.
Previously this code worked flawlessly, all I did was remove one or two elements from the preset default list of elements, and in the simulator, simulated deleting the app, and recompiled the code. Ever since my program crashes, with the above message, and I can't figure out how to fix it.
So if someone can give me some help on how to go about debugging this, that would be wonderful. I can attach code as needed, i'm not sure what code would be relevant to be shown, and it may be too much to post all the code involved in the project.
Code to encode and decode the properties of my custom Name NSObject class called name.h:
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeInteger:self.score forKey:#"score"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
if((self = [super init]))
{
self.name = [decoder decodeObjectForKey:#"name"];
self.score = [decoder decodeIntegerForKey:#"score"];
}
return self;
}
Retrieving Data from class, incase this matters, this code occurs in appdelegate.m:
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"dataArray"];
NSInteger score = 0;
NSMutableArray *temp = [[NSMutableArray alloc] initWithObjects:#"name", nil];
NSMutableArray *tempList = [[NSMutableArray alloc] init];
for(NSString *y in temp)
{
Name *name = [[Name alloc] init];
name.name = y;
name.score = score;
[tempList addObject:name];
}
if (data)
{
NSArray *list = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableArray *names = [[NSMutableArray alloc]initWithArray:list];
// [_nameList addObjectsFromArray:temp];
NSMutableArray *t = [[names arrayByAddingObjectsFromArray:tempList ] mutableCopy];
_nameList = [[NSMutableArray alloc]init];
[_nameList addObjectsFromArray:t];
}
else
{
//First time load or data is not saved yet
_nameList = [[NSMutableArray alloc] initWithObjects:#"name", nil];
}
saving the array at close time:
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
NSData *data =[NSKeyedArchiver archivedDataWithRootObject:_nameList];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"dataArray"];
}
same code is in applicationDidEnterBackground.
code to sort by 'score'
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController
{
if(viewController == _viewController3)
{
[self sortNames:_nameList];
[[(ThirdViewController*)_viewController3 topList] reloadData];
}
}
-(void)sortNames:(NSMutableArray*)test
{
NSArray* temp = [[NSArray alloc] initWithArray:test];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"score" ascending:NO];
NSArray *sortedLinks = [[temp sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]] mutableCopy];
_nameList = (NSMutableArray*) sortedLinks;
}
Here's my two cents. You have this line:
[encoder encodeObject:self.name forKey:#"name"];
And then this line:
Name *name = [[Name alloc] init];
This makes me think that the "self.name" property is one of these "Name" custom subclasses.
I believe that if you make a custom subclass and you want it to encodewithcoder, you have to add the encodewithcoder method to your custom subclass and have it encodewithcoder all of its properties and instance variables as primitively as you can.
Meaning, your Name class needs to have its own encodewithcoder method that encodes all of its properties and instance variables that have been stored as factory objects or c primitives.
Edit: I'm still pretty new and value my rep. If I'm wrong, please comment and I'll delete but please don't downvote me to oblivion
Here:
_nameList = [[NSMutableArray alloc] initWithObjects:#"name", nil];
you are adding an NSString to the _nameList array. Later you save that array.
The next time you load that array, you have the NSString #"name" in it. I guess at some point you iterate through the items in _nameList and try to get or set the score, since you are calling this on a subclass of NSString you get the NSUnknownKeyException.
I think what you want to is to replace the line above with something like this: (assuming the class in your name.h file you mentioned above is called Name)
Name *newName = [[Name alloc] init]; //or initialize the way you need to
_nameList = [[NSMutableArray alloc] initWithObjects:newName, nil];

Does anyone have example code using encodeBytes:length:forKey:?

I need it for my NSCoder classes apparently and it would be useful if I can see a real example of this and its corresponding method.
The reason I feel I need it is when my method tries to encodeObject for an NSString*, it complains saying:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -encodeBytes:length:forKey: only defined for abstract class. Define -[JSONEncoder encodeBytes:length:forKey:]!'
Thanks.
The method in JSONEncoder: (https://github.com/ontometrics/JSONCoding) I couldn't get the static library thing to work, so I'm trying to work directly with the code.
- (void)encodeObject:(id)object {
[self push:object];
[object encodeWithCoder:self];
finalJSONObject = [NSDictionary dictionaryWithObject:[self topObject] forKey:[[[object class] description] camelcaseString]];
[self pop];
}
I think that the JSONEncoder is mainly meant to encode objects from custom classes (which must implement the NSCoding protocol). For example, if you define a class Person with a string property:
#interface Person : NSObject <NSCoding>
#property(strong, nonatomic) NSString *name;
#end
#implementation Person
- (id)initWithCoder:(NSCoder *)coder {
if ((self = [super init])) {
self.name = [coder decodeObjectForKey:#"name"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:#"name"];
}
#end
then you can encode an object of this class without problems using JSONEncoder:
JSONEncoder *encoder = [JSONEncoder encoder];
Person *obj = [[Person alloc] init];
obj.name = #"John";
[encoder encodeObject:obj];
NSString *json = [encoder json];
NSLog(#"%#", json);
// Output: {"person":{"name":"John"}}
It works also with some "plain" Foundation types, e.g. with NSNumber, but the output is not very "nice", and I am not sure if JSONEncoder is meant to be used like that:
JSONEncoder *encoder = [JSONEncoder encoder];
NSNumber *obj = #1234;
[encoder encodeObject:obj];
NSString *json = [encoder json];
NSLog(#"%#", json);
// Output: {"__NSCFNumber":{"NS.intval":1234}}
In this case, the encodeInt64:forKey: method of JSONEncoder is called internally.
However, as you noticed, it does not work with NSString:
JSONEncoder *encoder = [JSONEncoder encoder];
NSString *obj = #"Hello world";
[encoder encodeObject:obj];
// Exception: *** -encodeBytes:length:forKey: only defined for abstract class. Define -[JSONEncoder encodeBytes:length:forKey:]!
The reason for the exception is that NSString is NSCoding compliant, but it uses the -encodeBytes:length:forKey: of the coder, and JSONEncoder does not implement this method.
Implementing that method is not too difficult, this is a quick-and-dirty attempt:
- (void)encodeBytes:(const uint8_t *)bytesp length:(NSUInteger)lenv forKey:(NSString *)key
{
NSString *s = [[NSString alloc] initWithBytes:bytesp length:lenv encoding:NSUTF8StringEncoding];
[self setObject:s forKey:key];
}
If you add this code to "JSONEncoder.m", then you can encode a plain NSString without getting an exception, the result looks like this:
{"__NSCFConstantString":{"NS.bytes":"Hello world"}}
But again, I am not sure if JSONEncoder is meant to be used like this.

NSCoding and saving/loading default values issue

I'm writing some code for an iPhone app, and I'm having issues getting default data to load in correctly. I am basing my code off some example from the "Learning Cocos2d" book by Ray Wenderlich.
It seems that even when I delete the app outright and try to start from fresh data that the app inconsistently either doesn't try to load the data, or incorrectly thinks that there is data, and loads null.
I'm using containsValueForKey to check if a value exists and then load it or load some default value, but even on a fresh installation the containsValueForKey finds data and doesn't load the defaults. In xcode's organizer I checked my device's file structure and the Documents folder, where I specified to save, doesn't look like it contains any files, so I'm not sure what it's grabbing.
My guess is that the problem is something to do with the initWithCoder function. It seems to mysteriously go through the function sometimes, but not all the time. Another weird thing is that I call [[GameManager sharedGameManager] save] when the player gets a highscore (not shown here, but the code is the exact same as this objectiveList, only an int) and it appears to save it correctly.
And now the code:
GCDatabase.h
#import <Foundation/Foundation.h>
id loadData(NSString * filename);
void saveData(id theData, NSString *filename);
GCDatabase.m
#import "GCDatabase.h"
NSString * pathForFile(NSString *filename) {
// 1
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
// 2
NSString *documentsDirectory = [paths objectAtIndex:0];
// 3
return [documentsDirectory stringByAppendingPathComponent:filename];
}
id loadData(NSString * filename) {
NSString *filePath = pathForFile(filename);
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *data = [[[NSData alloc] initWithContentsOfFile:filePath] autorelease];
NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
id retval = [unarchiver decodeObjectForKey:#"Data"];
[unarchiver finishDecoding];
return retval;
}
return nil;
}
void saveData(id theData, NSString *filename) {
NSMutableData *data = [[[NSMutableData alloc] init] autorelease];
NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
[archiver encodeObject:theData forKey:#"Data"];
[archiver finishEncoding];
[data writeToFile:pathForFile(filename) atomically:YES];
}
GameManager.h
#interface GameManager : NSObject <NSCoding>{
NSMutableArray *objectiveDescriptions;
}
#property (nonatomic, retain) NSMutableArray * objectiveDescriptions;
+(GameManager*)sharedGameManager;
-(void)save;
-(void)load;
-(void)encodeWithCoder:(NSCoder *)encoder;
-(id)initWithCoder:(NSCoder *)decoder;
#end
GameManager.m (I added the load function, in an attempt to force it to load, but it doesn't seem to work)
+(GameManager*)sharedGameManager {
#synchronized([GameManager class])
{
if(!sharedGameManager) {
sharedGameManager = [loadData(#"GameManager") retain];
if (!sharedGameManager) {
[[self alloc] init];
}
}
return sharedGameManager;
}
return nil;
}
+(id)alloc {
#synchronized([GameManager class]){
NSAssert(sharedGameManager == nil, #"Attempted to allocate a second instance of the Game Manager singleton");
sharedGameManager = [super alloc];
return sharedGameManager;
}
return nil;
}
- (void)dealloc {
[objectiveList release];
[super dealloc];
}
- (void)save {
saveData(self, #"GameManager");
}
-(void)load {
loadData(#"GameManager");
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:objectiveList forKey:#"objectiveList"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
if ([decoder containsValueForKey:#"objectiveList"]) {
objectiveList = [decoder decodeObjectForKey:#"objectiveList"];
} else {
[objectiveList addObjectsFromArray:[NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
}
}
return self;
}
#end
I have not read your full code.. But I found a problem in code....
You have not allocated memory to objectiveList array.. Unless and until you allocate memory to array, objects will not be added...
I think go for
objectiveList = [[NSMutableArray alloc] initWithArray:[NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
instead of
[objectiveList addObjectsFromArray:[NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
Check for the syntax.. Hope this may help as it troubled me also in the past where I forgot to allocate memory to the array.. And kept on adding objects resulting in null... :)
In case it doesn't solve your problem, I'll look for code later completely.. :)
I seem to see the problem. When the constructor is called the first time, the objectiveList is not even created as the "initWithCoder" is never called. You have to override the init method as well in order for the objectiveList array to be constructed. Basically, the code that is calling the init method is in here:
+(GameManager*)sharedGameManager {
#synchronized([GameManager class])
{
if(!sharedGameManager) {
sharedGameManager = [loadData(#"GameManager") retain];
if (!sharedGameManager) {
[[self alloc] init]; // GOES INTO INIT METHOD, NOT INITWITHCODER!
}
}
return sharedGameManager;
}
return nil;
}
On a side note, that singleton implementation gave me a headache. Just saying. :)
There is (as far as I can see from the code you have provided) a logic flaw in your code. Consider what would happen if decoder did not contain an objectiveList key; the else clause would execute, but you never allocated objectiveList so the addObjectsFromArray: call will silently fail.
To test this theory, alter your code as show below, and rerun. If the assertion fires then the above theory is correct, if not you need to hunt a bit more!
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self != nil)
{
if ([decoder containsValueForKey:#"objectiveList"])
{
objectiveList = [decoder decodeObjectForKey:#"objectiveList"];
}
else
{
NSAssert(objectiveList, #"objectiveList must be non-nil to add objects.");
[objectiveList addObjectsFromArray[NSArrayarrayWithObjects:#"1",#"2",#"3",#"4",#"5", nil]];
}
}
return self;
}
By the way, objectiveList is never declared as an ivar... I am sort of assuming that objectiveList and objectiveDescriptions are meant to be the same.
The method in GameManager.m should look like this:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
if ([decoder containsValueForKey:#"objectiveList"]) {
objectiveList = [[decoder decodeObjectForKey:#"objectiveList"] retain];
} else {
objectiveList = [[NSMutableArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
}
}
You have two cases: either objectiveList is present, in which case you have previously saved some data, or it is not present and you need to create the default data (1, 2, 3, 4, 5). In the code above, I have changed the first case to retain the array returned by decodeObjectForKey, since Apple's docs state that this method returns an autorelease object. You need to retain it here to prevent the memory from being reused for some other objects that are created later in your app. By not retaining objectiveList, when accessing it later you were probably accessing garbage results (i.e. random memory) rather than what you had just decoded.
On a similar note, in the second case where objectiveList was not already present - i.e. for a new install of the app where there is no saved data present - you are not allocating objectiveList before trying to add objects to it. I have changed this line to actually alloc the object (and therefore the memory required), and then init with the default values you want. Since you were previously trying to add items to an array that had not been created, you would again get garbage data when trying to access the values from it. Note that I assume you are using an NSMutableArray here, but you might also be using an NSMutableSet.

Resources