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

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)

Related

Init and init with parameters

Here is the code :
#implementation Accumulateur
// Constructor
- (id) init
{
return ([self initWithTotal:0]);
}
- (id) initWithTotal:(int)aTotal
{
AccumulateurMoyen *ac;
if ((ac = [[AccumulateurMoyen alloc] init]) == nil)
{
[self release];
return (nil);
}
return ([self initWithTotal:aTotal andAccumulateurMoyen:ac]);
}
- (id) initWithTotal:(int)aTotal
andAccumulateurMoyen:(AccumulateurMoyen *)aAcMoyen
{
if (self = [super init])
{
[aAcMoyen retain];
[acMoyen release];
acMoyen = aAcMoyen;
total = aTotal;
}
return (self);
}
#end
The problem is here : if ((ac = [[AccumulateurMoyen alloc] init]) == nil)
As I redefined init, the init called is mine and not that of NSObject...
I dont have idea, how i can do that correctly (AccumulateurMoyen is subclass of Accumulateur)
Thx you
You have probably undesired recursion there: [[AccumulateurMoyen alloc] init] tries to create new AccumulateurMoyen but that results in nested initWithTotal: which again tries to create another AccumulateurMoyen instance etc.
I.e. your code tries to create Accumulateur which has member acMoyen set to new instance of AccumulateurMoyen, which again has its acMoyen set to another new instance of AccumulateurMoyen etc.
You must to break the endless recursion. E.g. in initWithTotal:, replace the line
if ((ac = [[AccumulateurMoyen alloc] init]) == nil)
with
if ((ac = [[AccumulateurMoyen alloc] initWithTotal:0 andAccumulateurMoyen:nil]) == nil)
I.e. the nested AccumulateurMoyen will have its member set to nil.
Sorry but I think you have a structural problem here. Why your super class need to have an reference to a class that extend it? I think your best option to it is think again how your class structure will be.
But in your subClass you can change the init method to so your problem will disappear.
(id)init {
return ([NSObject init]);
}

Can I pass delegate as a parameter objective-c

I am working with an NSOperationQueue and I want to add new NSOperations to the NSOperationQueue. It is a queue that lives in a singleton instance of a class I have. It would make things a lot easier if I could move everything into the static class by passing the delegate.
Here is my code now as it lives in - this is in a cellForRowAtIndexPath
NSString *key = [NSString stringWithFormat:#"%#%#",cell.dataItem.ItemID, cell.dataItem.ManufacturerID];
if (![self.imgOperationInQueue valueForKey:key]) {
ImageOperation *imgOp = [[ImageOperation alloc] initWithItemID:cell.dataItem.ItemID withManufacturerID:cell.dataItem.ManufacturerID withReurnType:kThumbnail];
imgOp.identifier = [NSString stringWithFormat:#"%i", cell.tag];
imgOp.delegate = self;
[[SharedFunctions sharedInstance] addImageOperationToQueue:imgOp];
[imgOp release];
// store these in the dictionary so we don;t queue up requests more than once
[self.imgOperationInQueue setValue:cell.dataItem.ItemID forKey:key];
}
If I could add the delegate as a parameter I could put all of this code into the shared singleton class and call it from anywhere in my app.
I suppose that I could use an NSNotification - or can I use a block of some sort?
Just create the appropriate init method that passes in the delegate.
- (id)initWithItemID:(NSString *)itemID
withManufacturerID:(NSString *)manufacturerID
withReurnType:(NSInteger)type
delegate:(id<YourDelegate>)theDelegate
{
self = [super init];
if (self)
{
.... // Other assignments
self.delegate = theDelegate;
}
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.

Do I need to allocate NSStrings passed in as parameters to a custom initialization method?

Please consider the following two initialization methods.
The first method simply passes the value of the parameters to their respective NSString properties, but the second allocates the properties and then initializes them using the initWithString: method. Is the allocation in the latter example necessary?
Thanks in advance.
-(id)initWithTitle:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = theTitle;
muscleGroup = theMuscleGroup;
equipment = theEquipment;
}
return self;
}
-(id)initWithTitle2:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [[NSString alloc] initWithString:theTitle];
muscleGroup = [[NSString alloc] initWithString:theMuscleGroup];
equipment = [[NSString alloc] initWithString:theEquipment];
}
return self;
}
The first example is not safe because you are not taking ownership of the strings, so your program will get all crashy if they are later released elsewhere. The second example fixes that problem and will work perfectly well, but is more concisely written thusly:
-(id)initWithTitle2:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [theTitle copy];
muscleGroup = [theMuscleGroup copy];
equipment = [theEquipment copy];
}
return self;
}
NSString gives you a copy constructor (-initWithString:), which enables you to do what you are doing in #2, but not all classes do. copy requires the class to implement the NSCopying protocol, but is more conformant with the way a Cocoa API developer would expect to be able to copy objects.
Parameter objects don't get copied when you pass them in. So your first example may not always work, it depends how you've initialized your strings.
The following is safer (although remember to release the objects in your dealloc method):
-(id)initWithTitle:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [theTitle retain];
muscleGroup = [theMuscleGroup retain];
equipment = [theEquipment retain];
}
return self;
}
Example 1 will assign the pointers. It makes no attempt to retain the objects and is vulnerable to something outside changing the content of the objects.
It could work depending on how the arguments are constructed in the first place;
Example 2 will copy the string objects and retain them. As long as you release in the dealloc then its the preferable method.
FWIW
title = [theTitle copy];
or
title = [[NSString stringWithString:theTitle] retain];
are equally good in Ex 2

How to restore an array with NSCoder

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