views:

21

answers:

1

Hi

I'm writing an iPhone app, and having problems trying to archive the settings. I am using a class (AppData) to hold settings (the example shows just one at present), and using the App Delegate to create an instance of AppData when the app loads, and store it when the app terminates. I am conforming (I think) to the NSCoding protocol

The app stores the file in the Documents directory OK, and checking the file structure seems to hold the data I would expect. However when the file is loaded in the unarchiver returns the error * Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver decodeObjectForKey:]: unarchiver has finished; cannot decode anything more'

I have been wrestling with this one for a while now, so if anyone can see the problem I'd be very grateful.

Code:

App Delegate:

interface:

#import <UIKit/UIKit.h>
#import "AppData.h"
#import "Constants.h"

@interface iLeanAppDelegate : NSObject <UIApplicationDelegate> {
    AppData *appData;
    UIWindow *window;
    UITabBarController *tabBarController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
@property (nonatomic, retain) AppData *appData;

@end

implementation:

#import "iLeanAppDelegate.h"


@implementation iLeanAppDelegate

@synthesize window;
@synthesize tabBarController; 
@synthesize appData;

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

    appData = [[AppData alloc] init];
    if ([[NSFileManager defaultManager] fileExistsAtPath:[AppData dataFilePath]]) {         //If previous settings have been found retrieve and initialise
        NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[AppData dataFilePath]];
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        [unarchiver finishDecoding];    
    appData = [unarchiver decodeObjectForKey:kDataKey];
        [unarchiver finishDecoding];

    [unarchiver release];
    [data release];
}

// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
}

-(void)applicationWillTerminate:(NSNotification *)notification {
    NSMutableData *data = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:appData forKey:kDataKey];
    [archiver finishEncoding];
    BOOL success = [data writeToFile:[AppData dataFilePath] atomically:YES];
    if (success)
        NSLog(@"OK");
    else
        NSLog(@"Problem here");
    [archiver release];
    [data release];

}

- (void)dealloc {
    [tabBarController release];
    [window release];
[appData release];
    [super dealloc];
}

@end

AppData class:

Interface:

#import <Foundation/Foundation.h>
#import "Constants.h"

@interface AppData : NSObject <NSCoding, NSCopying> {

    BOOL audioAlert;        //YES = audio alerts are on, NO = audio alerts are off
}

+(NSString *)dataFilePath;

@property(nonatomic, assign) BOOL audioAlert;

@end

Implementation:

#import "AppData.h"


@implementation AppData

@synthesize audioAlert;


+(NSString *)dataFilePath {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    return [documentsDirectory stringByAppendingPathComponent:kSettingsFilename];
}


#pragma mark NSCoding

-(void)encodeWithCoder:(NSCoder *)encoder {

    [encoder encodeBool:audioAlert forKey:kSettingsKey];
}

-(id)initWithCoder:(NSCoder *)decoder {
    if (self = [super init]) {
        self.audioAlert = [decoder decodeObjectForKey:kSettingsKey];
    }

    return self;
}

#pragma mark -
#pragma mark NSCopying

-(id)copyWithZone:(NSZone *)zone {
    AppData *copy = [[[self class] allocWithZone: zone] init];
//Will do this once coding works
    return copy;
}

@end

Finally constants.h

//This file contains all the application constants

#define kSettingsFilename   @"archive"  //Filename where all application settings are stored

#define kSettingsKey        @"settingsKey"      //Key name
#define kDataKey            @"data"
+1  A: 

Just as the error message states, you are calling finishDecoding before you have finished decoding. And then you call it again:

[unarchiver finishDecoding];    
appData = [unarchiver decodeObjectForKey:kDataKey];
[unarchiver finishDecoding];

That cannot work.

That said, your code is more complicated than it needs to be. Why not this:

// Encoding:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:appData];

// Decoding:
appData = [NSKeyedUnarchiver unarchiveObjectWithData:data];
Ole Begemann
Thanks Ole, not sure how many times I have read through that code and missed something quite so obvious.
Ross Catley
And I'm all for simplification, thanks for the tip.
Ross Catley