Search code examples
objective-ccocoanskeyedarchivernskeyedunarchiver

NSKeyedUnarchiver finds corrupted data


I have an archiving program and an unarchiving program that work together.

The run results of the second program shows that although one of the two objects (myFoo1) is well archived, stored and unarchived later on, the other (myBook) seems unable to be properly unarchived. I just can't know what happens in the archiving and/or unarchiving processes that seems to have corrupted part of the original data. I've carefully ensured that the encoding keys are identical to the decoding keys but the program persists.

Code for the archiving program:

#import <Foundation/Foundation.h>

@interface Foo : NSObject <NSCoding>

@property (copy, nonatomic) NSString * strVal;
@property int intVal;
@property float floatVal;

-(void) display;

@end

#import "Foo.h"

@implementation Foo

@synthesize  strVal, intVal, floatVal;

-(void) encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject: strVal forKey: @"FoostrVal"];
    [aCoder encodeInt: intVal forKey: @"FoointVal"];
    [aCoder encodeFloat: floatVal forKey: @"FoofloatVal"];
    // NSLog (@"Foo encodeWithCoder called"); called
}

-(id) initWithCoder:(NSCoder *)aDecoder
{
    strVal = [aDecoder decodeObjectForKey: @"FoostrVal"];
    intVal = [aDecoder decodeIntForKey: @"FoointVal"];
    floatVal = [aDecoder decodeFloatForKey: @"FoofloatVal"];
    // NSLog (@"Foo initWithCoder called"); not called
    return self;
}

-(void) display
{
    NSLog (@"%@ %i %g", strVal, intVal, floatVal);
}

@end

#import <Foundation/Foundation.h>

@interface AddressCard : NSObject <NSCoding>

@property (nonatomic, copy) NSString * name, * email;

-(instancetype) initWithName: (NSString *) theName andEmail: (NSString *) theEmail;
-(void) showCard;

@end

#import "AddressCard.h"

@implementation AddressCard

@synthesize name, email;

-(instancetype) initWithName: (NSString *) theName andEmail: (NSString *) theEmail
{
    self = [super init];
    if (self) {
        self.name = [NSString stringWithString: theName];
        self.email = [NSString stringWithString: theEmail];
    }
    return self;
}

-(instancetype) init
{
    return [self initWithName: @"NoName" andEmail: @"NoEmail"];
}

-(void) encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject: name forKey: @"AddrCardName"];
    [aCoder encodeObject: email forKey: @"AddrCardEmail"];
    // NSLog (@"AddressCard encodeWithCoder called"); called
}

-(id) initWithCoder:(NSCoder *)aDecoder
{
    [aDecoder decodeObjectForKey: @"AddrCardName"];
    [aDecoder decodeObjectForKey: @"AddrCardEmail"];
    // NSLog (@"AddressCard initWithCoder called"); not called

    return self;
}

-(void) showCard
{
    NSLog (@"%-20s%-40s", [name UTF8String], [email UTF8String]);
}

@end

#import "AddressCard.h"

@interface AddressBook : NSObject <NSCoding>

@property (nonatomic, copy) NSString * title;
@property (nonatomic, strong) NSMutableArray * book;

-(instancetype) initWithTitle: (NSString *) title;
-(void) addCard: (AddressCard *) card;
-(void) list;

@end

#import "AddressBook.h"

@implementation AddressBook

@synthesize title, book;

-(instancetype) initWithTitle: (NSString *) theTitle
{
    self = [super init];
    if (self) {
        title = [NSString stringWithString: theTitle];
        book = [NSMutableArray array];
    }
    return self;
}

-(instancetype) init
{
    return [self initWithTitle: @"NoTitle"];
}

-(void) encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject: title forKey: @"AddrBookTitle"];
    [aCoder encodeObject: book forKey: @"AddrBookBook"];
    // NSLog (@"AddressBook encodeWithCoder called"); called
}

-(id) initWithCoder:(NSCoder *)aDecoder
{
    [aDecoder decodeObjectForKey: @"AddrBookTitle"];
    [aDecoder decodeObjectForKey: @"AddrBookBook"];
    // NSLog (@"AddressBook initWithCoder called"); not called

    return self;
}

-(void) addCard: (AddressCard *) card
{
    if ([book containsObject: card] == YES)
        NSLog (@"The card already exists in %@", title);
    else
        [book addObject: card];
}

-(void) list
{
    for (AddressCard * card in book)
        [card showCard];
}

@end

#import "AddressBook.h"
#import "Foo.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Foo * myFoo1 = [[Foo alloc] init];
        NSMutableData * dataArea;
        NSKeyedArchiver * archiver;
        AddressBook * myBook = [[AddressBook alloc] initWithTitle: @"Steve's Address Book"];

        NSString * aName = @"Julia Kochan";
        NSString * aEmail = @"[email protected]";
        NSString * bName = @"Tony Iannino";
        NSString * bEmail = @"[email protected]";
        NSString * cName = @"Stephen Kochan";
        NSString * cEmail = @"steve@steve_kochan.com";
        NSString * dName = @"Jamie Baker";
        NSString * dEmail = @"[email protected]";
        NSString * filePath = @"addrbook.arch";

        AddressCard * card1 = [[AddressCard alloc] initWithName: aName andEmail: aEmail];
        AddressCard * card2 = [[AddressCard alloc] initWithName: bName andEmail: bEmail];
        AddressCard * card3 = [[AddressCard alloc] initWithName: cName andEmail: cEmail];
        AddressCard * card4 = [[AddressCard alloc] initWithName: dName andEmail: dEmail];

        // Add some cards to the address book
        [myBook addCard: card1];
        [myBook addCard: card2];
        [myBook addCard: card3];
        [myBook addCard: card4];

        myFoo1.strVal = @"This is the string";
        myFoo1.intVal = 12345;
        myFoo1.floatVal = 99.8;

        // Set up a data area and connect it to an NSKeyedArchiver object
        dataArea = [NSMutableData data];
        archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: dataArea];

        // Now we can begin to archive objects
        [archiver encodeObject: myBook forKey: @"myaddrbook"];
        [archiver encodeObject: myFoo1 forKey: @"myfoo1"];
        [archiver finishEncoding];

        // Write the archived data area to a file
        if ([dataArea writeToFile: filePath atomically: YES] == NO)
            NSLog (@"Archiving failed!");
        NSLog (@"All operations successful!");

    }
    return 0;
}

Code for the unarchiving program:

#import <Foundation/Foundation.h>

@interface Foo : NSObject <NSCoding>

@property (copy, nonatomic) NSString * strVal;
@property int intVal;
@property float floatVal;

-(void) display;

@end

#import "Foo.h"

@implementation Foo

@synthesize  strVal, intVal, floatVal;

-(void) encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject: strVal forKey: @"FoostrVal"];
    [aCoder encodeInt: intVal forKey: @"FoointVal"];
    [aCoder encodeFloat: floatVal forKey: @"FoofloatVal"];
    // NSLog (@"Foo encodeWithCoder called"); not called
}

-(id) initWithCoder:(NSCoder *)aDecoder
{
    strVal = [aDecoder decodeObjectForKey: @"FoostrVal"];
    intVal = [aDecoder decodeIntForKey: @"FoointVal"];
    floatVal = [aDecoder decodeFloatForKey: @"FoofloatVal"];
    // NSLog (@"Foo initWithCoder called"); called

    return self;
}

-(void) display
{
    NSLog (@"%@\n%i\n%g", strVal, intVal, floatVal);
}

@end

#import <Foundation/Foundation.h>

@interface AddressCard : NSObject <NSCoding>

@property (nonatomic, copy) NSString * name, * email;

-(instancetype) initWithName: (NSString *) theName andEmail: (NSString *) theEmail;
-(void) showCard;

@end

#import "AddressCard.h"

@implementation AddressCard

@synthesize name, email;

-(instancetype) initWithName: (NSString *) theName andEmail: (NSString *) theEmail
{
    self = [super init];
    if (self) {
        self.name = [NSString stringWithString: theName];
        self.email = [NSString stringWithString: theEmail];
    }
    return self;
}

-(instancetype) init
{
    return [self initWithName: @"NoName" andEmail: @"NoEmail"];
}

-(void) encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject: name forKey: @"AddrCardName"];
    [aCoder encodeObject: email forKey: @"AddrCardEmail"];
    // NSLog (@"AddressCard encodeWithCoder called"); not called
}

-(id) initWithCoder:(NSCoder *)aDecoder
{
    [aDecoder decodeObjectForKey: @"AddrCardName"];
    [aDecoder decodeObjectForKey: @"AddrCardEmail"];
    // NSLog (@"AddressCard initWithCoder called"); called

    return self;
}

-(void) showCard
{
    NSLog (@"%-20s%-40s", [name UTF8String], [email UTF8String]);
}

@end

#import "AddressCard.h"

@interface AddressBook : NSObject <NSCoding>

@property (nonatomic, copy) NSString * title;
@property (nonatomic, strong) NSMutableArray * book;

-(instancetype) initWithTitle: (NSString *) title;
-(void) addCard: (AddressCard *) card;
-(void) list;

@end

#import "AddressBook.h"

@implementation AddressBook

@synthesize title, book;

-(instancetype) initWithTitle: (NSString *) theTitle
{
    self = [super init];
    if (self) {
        title = [NSString stringWithString: theTitle];
        book = [NSMutableArray array];
    }
    return self;
}

-(void) encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject: title forKey: @"AddrBookTitle"];
    [aCoder encodeObject: book forKey: @"AddrBookBook"];
    // NSLog (@"AddressBook encodeWithCoder called"); not called

}

-(id) initWithCoder:(NSCoder *)aDecoder
{
    [aDecoder decodeObjectForKey: @"AddrBookTitle"];
    [aDecoder decodeObjectForKey: @"AddrBookBook"];
    // NSLog (@"AddressBook initWithCoder called"); called

    return self;
}

-(void) addCard: (AddressCard *) card
{
    if ([book containsObject: card] == YES)
        NSLog (@"The card already exists in %@", title);
    else
        [book addObject: card];
}

-(void) list
{
    for (AddressCard * card in book)
        [card showCard];
}

@end

#import "AddressBook.h"
#import "Foo.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSData * dataArea;
        NSKeyedUnarchiver * unarchiver;
        Foo * myFoo1;
        AddressBook * myBook;
        NSString * filePath = @"addrbook.arch";

        // Read in the archive and connect an
        // NSkeyedUnarchiver object to it

        dataArea = [NSData dataWithContentsOfFile: filePath];
        if (!dataArea) {
            NSLog (@"Can't read back archive file!");
            return 1;
        }
        unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData: dataArea];
        // Decode the objects we previously stored in the archive
        myBook = [unarchiver decodeObjectForKey: @"myaddrbook"];
        myFoo1 = [unarchiver decodeObjectForKey: @"myfoo1"];
        [unarchiver finishDecoding];

        // Verify that the resote was successful
        if (myBook != nil) {
            if ([myBook.book count] == 0)
                NSLog (@"Data corrupted in myBook!");
            else
                [myBook list];
        }
        else
            NSLog (@"Load myBook failed!");

        if (myFoo1 != nil)
            [myFoo1 display];
        else
            NSLog (@"Load myFoo1 failed!");
    }
    return 0;
}

Run result for the above unarchiving program: Data corrupted in myBook! This is the string 12345 99.8 Program ended with exit code: 0

Screenshot for run result in Xcode:


Solution

  • There are two issues here: your AddressBook and AddressCard -initWithCoder: implementations neither

    1. Call self = [super init]. This isn't the root cause of the problem, but is certainly not correct
    2. Nor assign the results of decoding to any of your properties. You decode AddrBookBook, but never assign it to self.book; thus, myBook.book == nil, and so [myBook.book count] == 0. All of the decode calls should read myIvar = [aDecoder decodeObjectForKey:...]