views:

477

answers:

3

I'm having trouble setting up a model object to save the visual state of user generated CALayers in a simple graphics application for the iphone.

I'm attempting to save the background color and frame of all the current layers on screen by passing those properties to model objects which implement the NSCoding protocol and then into an NSMutableArray which the app delegate owns. Then I archive the array with NSKeyedArchiver and store it in the NSUserDefaults.

Each CALayer's backgroundColor property is converted to a UIColor to be encoded by the model object for storage. I think that I'm unarchiving the array incorrectly or not restoring state from the unarchived array correctly. When I attempt to access the UIColor object that was store in the model object, I get an EXC_BAD_ACCESS.

I thought it was possibly a bug with encoding UIColor objects so tried pulling the values out of the CGColorRef with the CGColorGetComponents function and storing them in an array to encode and archive, but I had the same result of bad access after unarchiving, so I think I'm just doing it wrong.

This is my model object:

@interface AILayerData : NSObject <NSCoding> {

    UIColor* color;
    CGRect  frame;
}

@property (retain) UIColor* color;
@property (assign) CGRect   frame;

@end


@implementation AILayerData

@synthesize color;
@synthesize frame;

- (void)encodeWithCoder:(NSCoder *)coder; 
{
    [coder encodeObject:color forKey:@"color"];
    [coder encodeCGRect:frame forKey:@"frame"];
}


- (id)initWithCoder:(NSCoder *)coder;
{
    self = [[AILayerData alloc] init];
    if (self != nil)
    {
     color = [coder decodeObjectForKey:@"color"];
     frame = [coder decodeCGRectForKey:@"frame"];
    }
    return self;
}

@end

And this is my archiving implementation:

@implementation AppDelegate

- (void)applicationWillTerminate:(UIApplication *)application {

    NSArray *layersArray = viewController.view.layer.sublayers;

    dataArray = [NSMutableArray array]; 

    for(AILayer *layer in layersArray)
    {
     AILayerData *layerData = [[AILayerData alloc] init];

     layerData.frame = layer.frame;

     UIColor *layerColor = [UIColor colorWithCGColor:layer.backgroundColor];

     layerData.color = layerColor;    

     [dataArray addObject:layerData];

     [layerData release];

    }

    [[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:layerDataArray] forKey:@"savedArray"];
}

@end

And here is where I restore state:

@implementation ViewController

- (void)viewDidLoad 
{    
    [super viewDidLoad];

    spaceView = [[AISpaceView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 
    self.view = spaceView;

    [spaceView release];

    spaceView.delegate = self;

    NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];

    NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];

    if (dataRepresentingSavedArray != nil) {

     [self restoreStateWithData:dataRepresentingSavedArray];

    }
}

- (void)restoreStateWithData:(NSData *)data 
{   
    NSArray *savedLayers = [NSKeyedUnarchiver unarchiveObjectWithData:data];

    if (savedLayers != nil) {

     NSArray *restoredLayers = [[NSArray alloc] initWithArray:savedLayers];

     for(AILayerData *layerDataObject in restoredLayers) {      

      UIColor *layerColor = layerDataObject.color;

      AILayer *newLayer = [[AILayer alloc] init];   

      newLayer.backgroundColor = layerColor.CGColor;

      newLayer.frame = layerDataObject.frame;   

      newLayer.isSelected = NO;

      [self.view.layer addSublayer:newLayer];

      [newLayer release];         
     }

     [restoredLayers release];

     [spaceView.layer layoutSublayers];  
    }
}

@end

Any help with this is greatly appreciated. I'm pretty much a noob. I was encoding, archiving and unarching an NSArray of NSNumbers converted from the color's floats in pretty much the same way and getting bad access.

A: 

You are over-releasing layerColor: You don't own it (layerDataObject does), but you are releasing it.

Peter Hosey
Ok. I've taken that out. But it doesn't get that far. As soon as I try to access layerDataObject.color, it crashes. Is this the correct way to restore an array from archived data?
Joe Ricioppo
It's correct. What does `-[AILayerData initWithCoder:]` look like? (You'll need to edit your question, of course.)
Peter Hosey
The implementation is at the top. But I'm not calling that method directly. Do I need to do that? From looking at samples and the documentation, it looks like unarching objects re-initializes all of the objects and calls initWithCoder: in the background. Is that correct?
Joe Ricioppo
Ah, now I see it. Correct, the unarchiver sends `initWithCoder:` to your objects for you. You need to retain those objects there, though. See: http://developer.apple.com/documentation/Cocoa/Conceptual/Archiving/Tasks/codingobjects.html#//apple_ref/doc/uid/20000948-97984
Peter Hosey
+1  A: 

You certainly want to retain the color in initWithCoder:

color = [[coder decodeObjectForKey:@"color"] retain];

or, with the dot syntax as color was declared as a retain property:

self.color = [coder decodeObjectForKey:@"color"];
0xced
Does the retain attribute in the property declaration not do the same thing?
Joe Ricioppo
See my updated answer for the property declaration.
0xced
A: 

It looks like NSCoder for iPhone doesn't respond to -encodeWithCGRect:

Source: http://17.254.2.129/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSCoder_Class/Reference/NSCoder.html

jbrennan