How to restore an array with NSCoder - objective-c

So far I have the following:
- (id)initWithCoder:(NSCoder*) coder
{
self = [super initWithCoder: coder];
if (self) {
// Call a setup method
}
return self;
}
Am I supposed to put the code to load the array in here? What could should I put and where should I put it?

You put myArray=[coder decodeObjectForKey:#"myArray"]; inside the if block.
If you haven't set up the encoding part of the code yet, to do that you just add a method:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:myArray forKey:#"myArray"];
}

Related

Is it possible for a NSObject subclass to "hook in" to every property access

I have an object that I serialize for saving to disk, and occasionally new properties get added when upgrading my app (think app prefs). A new property would not exist on an old object, so naturally [myObj myNewProperty] would evaluate to nil. I want to change this so I can return something relevant, and preferably do so without overriding the getter for every property. Is such a thing possible?
You can set a default value when decoding your object:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self.myNewProperty = [aDecoder decodeObjectForKey:#"myNewProperty"];
if(!self.myNewProperty)
{
self.myNewProperty = ...(The new value here!);
}
return self;
}
You can record the version while serializing and get back while deserializing then you can check if you are accessing old version object or a new one with additional properties like below
#define kVersionKey #"Version"
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeInt:1 forKey:kVersionKey];
// other encoding
}
- (id)initWithCoder:(NSCoder *)decoder {
int i=[decoder decodeIntForKey:kVersionKey];
if (i==1) {
// set your new property
}
// other decodings
}

Obj-C: NSError in initialiser

Fairly simple question:
I have an init method on my class that has the potential to go wrong. If it does, I plan to "return nil", but I would also like to return an error. Is it bad practice to have an NSError** parameter to an init method? My method declaration would look like this:
- (id) initWithArgs:(NSString*) args andError:(NSError**)error;
Many thanks,
Nick
It's unusual, but I don't think it's necessarily a bad practice. I'd name the second part of the method just "error" instead of "andError:", though. You don't need to connect the parts of a method name with 'and', and in this case it also gives the impression that the error is being used to initialize the object. Just make it:
- (id) initWithArgs:(NSString*) args error:(NSError**)error;
Also, don't forget to release the allocated object if you plan to return something else (like nil):
- (id) initWithArgs:(NSString*) args error:(NSError**)error
{
if ((self = [super init])) {
if (canInitThisObject) {
// init this object
}
else {
[self release];
self = nil;
if (error != nil) {
*error = [NSError errorWithDomain:someDomain code:someCode: userInfo:nil];
}
}
}
return self;
}

NSCoder - Encoding an Array, with multiple levels of nested arrays

I have a mainObjectArray (NSMutableArray) which is populated with instances of a custom class. Each instance is itself an array, and objects in each array are NSDates, NSStrings, BOOL, and more arrays containing similar objects.
What I haven't been able to establish is whether it's possible to, inside the
- (void)encodeWithCoder:(NSCoder *)encoder
method, to just say something like that:
[encoder encodeWithObject:mainObjectArray];
Or do have to encode every object in every instance separately? This would be a bit of a pain...
Your help would be very much appreciated.
Just implement the encoding and decoding methods in your custom class. That will do. Some sample,
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:[NSNumber numberWithInt:pageNumber] forKey:#"pageNumber"];
[encoder encodeObject:path forKey:#"path"];
[encoder encodeObject:array forKey:#"array"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super init])
{
self.pageNumber = [[aDecoder decodeObjectForKey:#"pageNumber"] intValue];
self.path = [aDecoder decodeObjectForKey:#"path"];
self.array = [aDecoder decodeObjectForKey:#"array"];
}
}
You can see totally three data types being encoded and decoded - int, string, array.
Hope this helps.

Why does ((self=[super init]]) work, but (!(self=[super init])) doesn't?

For aesthetic reasons, I decided to change this:
if ((self = [super init])) {
// init self
}
return self;
Into this:
if (!(self = [super init])) return nil;
// init self
return self;
In theory, they do the same thing. The first one is the classic way, simply works. Debugging the second one, I found that it almost worked. The "if" does it right, the init code also, but, after returning "self", the debugger get back to the "if" and returns nil!
All classes I made with the second one I'm reverting to use the "correct" way because they where initing with nil, but I really want to know why does it behaves like that! I'm afraid that this may be the result of something else wrong!
There's absolutely no difference between your two versions other than aesthetic preference, so something else must be going wrong. Perhaps you should post your whole init method?
I created a test class for this, with the following init method:
- (id)init
{
if (!(self = [super init])) return nil;
[self setText:#"foo"];
return self;
}
It initializes as expected, and I can access the text property. So as Nick pointed out, something else must be malfunctioning.

initWithCoder works, but init seems to be overwriting my objects properties?

I've been trying to teach myself how to use the Archiving/Unarchiving methods of NSCoder, but I'm stumped.
I have a Singleton class that I have defined with 8 NSInteger properties. I am trying to save this object to disk and then load from disk as needed.
I've got the save part down and I have the load part down as well (according to NSLogs), but after my "initWithCoder:" method loads the object's properties appropriately, the "init" method runs and resets my object's properties back to zero.
I'm probably missing something basic here, but would appreciate any help!
My class methods for the Singleton class:
+ (Actor *)shareActorState
{
static Actor *actorState;
#synchronized(self) {
if (!actorState) {
actorState = [[Actor alloc] init];
}
}
return actorState;
}
-(id)init
{
if (self = [super init]) {
NSLog(#"New Init for Actor started...\nStrength: %d", self.strength);
}
return self;
}
-(id)initWithCoder:(NSCoder *)coder
{
if (self = [super init]) {
strength = [coder decodeIntegerForKey:#"strength"];
dexterity = [coder decodeIntegerForKey:#"dexterity"];
stamina = [coder decodeIntegerForKey:#"stamina"];
will = [coder decodeIntegerForKey:#"will"];
intelligence = [coder decodeIntegerForKey:#"intelligence"];
agility = [coder decodeIntegerForKey:#"agility"];
aura = [coder decodeIntegerForKey:#"aura"];
eyesight = [coder decodeIntegerForKey:#"eyesight"];
NSLog(#"InitWithCoder executed....\nStrength: %d\nDexterity: %d", self.strength, self.dexterity);
[self retain];
}
return self;
}
-(void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInteger:strength forKey:#"strength"];
[encoder encodeInteger:dexterity forKey:#"dexterity"];
[encoder encodeInteger:stamina forKey:#"stamina"];
[encoder encodeInteger:will forKey:#"will"];
[encoder encodeInteger:intelligence forKey:#"intelligence"];
[encoder encodeInteger:agility forKey:#"agility"];
[encoder encodeInteger:aura forKey:#"aura"];
[encoder encodeInteger:eyesight forKey:#"eyesight"];
NSLog(#"encodeWithCoder executed....");
}
-(void)dealloc
{
//My dealloc stuff goes here
[super dealloc];
}
I'm a noob when it comes to this stuff and have been trying to teach myself for the last month, so forgive anything obvious.
Thanks for the help!
I use the following template all the time and find it very useful. The loading and saving of the singleton's state is encapsulated and all you have to do is to ask for the shared instance. You might want to make persistToStorage public and call it from the app delegate's applicationWillTerminate: method.
(You might want to make this more thread safe.)
static Actor* SharedActor;
+(Actor*)sharedActor
{
if (SharedActor)
return SharedActor;
SharedActor = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self actorDataFileName]]retain];
if (!SharedActor)
SharedActor = [[Actor alloc]init];
return SharedActor;
}
+(NSString*)actorDataFileName
{
NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [path stringByAppendingPathComponent:#"actor.dat"];
}
-(BOOL)persistToStorage
{
return [NSKeyedArchiver archiveRootObject:self toFile:[Actor userDataFileName]];
}
-(void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInteger:strength forKey:#"strength"];
. . .
}
-(id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init])
{
strength = [decoder decodeIntegerForKey:#"strength"];
. . .
}
}
A couple of things:
Don't do this: [self retain];
There is nothing strange about archiving a singleton...
I'm not sure that it makes sense to have a singleton class that is archived/unarchived. When you do Actor *actor = [Actor shareActorState]; you're accessing the shared instance. If you then archive that NSData *data = [NSKeyedArchiver archivedDataWithRootObject:actor], you'll be saving the correct thing.
What are you intending when you unarchive, though? Actor *newactor = [NSKeyedUnarchiver unarchiveObjectWithData:data]; creates a new actor. It's in a new area of memory from your shared actor. This newactor is different from the singleton, [Actor shareActorState], and to violates the design pattern since you now have more than one object.
More likely, you'd want to implement two instance methods on your Actor class. They would be:
- (NSData *)archiveStateToData;
- (void)unarchiveStateFromData:(NSData *)data;
Which would actually update the values in the instance (using archiving). If you're trying to learn the NSCoding protocol and how to archive/unarchive data, I would recommend not doing it with a singleton.
While I agree with #wbyoung that you probably shouldn't be archiving/unarchiving a singleton (unless you're going through NSUserDefaults), if you were to do this I believe it would work by just changing
-(id)initWithCoder:(NSCoder *)coder
{
if (self = [super init]) {
to
-(id)initWithCoder:(NSCoder *)coder
{
if (self = [self shareActorState]) {
(oh and I would rename shareActorState to sharedActorState, or better yet sharedActor)