Storing NSMutableArray filled with custom objects

2020-05-01 04:47发布

问题:

EDIT: Here is a working version. I was able to retrieve my object within the NSMUtableArray after I saved and loaded it from the NSUserDefaults via NSCoding. I think it's important to mention, that you not only need to de-archive the array, but also all its content. As you can see, I had to not only store the NSData of my freeze object, but also the NSData of my array:

// My class "Freeze"
@interface Freeze : NSObject <NSCoding> // The NSCoding-protocoll is important!!
{
    NSMutableString *name;
}
@property(nonatomic, copy) NSMutableString *name;

-(void) InitString;
@end

@implementation Freeze
@synthesize name;

-(void) InitString {
    name = [[NSMutableString stringWithString:@"Some sentence... lalala"] retain];
}

// Method from NSCoding-protocol
- (void)encodeWithCoder:(NSCoder *)encoder
{
    //Encode properties, other class variables, etc
    [encoder encodeObject:self.name forKey:@"name"];

}

// Method from NSCoding-protocol
- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if( self != nil )
    {
        //decode properties, other class vars
        self.name = [decoder decodeObjectForKey:@"name"];

    }
    return self;
}
@end




Freeze *freeze;
NSMutableArray *runes;
NSMutableArray *newRunes;


runes = [[NSMutableArray alloc] init];
newRunes = [[NSMutableArray alloc] init];

freeze = [[Freeze alloc] init];
[freeze InitString];
[runes addObject:freeze];

[self saveState];
[self restoreState];

Freeze *newFreeze = [[Freeze alloc] init];
newFreeze = [newRunes objectAtIndex:0];
NSString *String = [NSString stringWithString:newFreeze.name];
NSLog(@"%@", String);

//-----------------------------------------------------------------------------

- (void) saveState 
{    
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

    NSData* myClassData = [NSKeyedArchiver archivedDataWithRootObject:freeze];
    [defaults setObject:myClassData forKey:@"MyClass"];

    NSData* myClassArrayData = [NSKeyedArchiver archivedDataWithRootObject:runes];
    [defaults setObject:myClassArrayData forKey:@"MyClassArray"];

}

- (void) restoreState 
{    
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];

    NSData* myClassData = [defaults objectForKey:@"MyClass"];
    freeze = [NSKeyedUnarchiver unarchiveObjectWithData:myClassData];

    NSData* myClassArrayData = [defaults objectForKey:@"MyClassArray"];    
    NSArray *savedMyClassArray = [NSKeyedUnarchiver unarchiveObjectWithData:myClassArrayData];
    if( savedMyClassArray != nil ) 
        newRunes = [[NSMutableArray alloc] initWithArray:savedMyClassArray];    
    else
    newRunes =  [[NSMutableArray alloc] init];
}

EDIT: This is an error I got before, it doesn't show up anymore with the updated version above.

It crashes at the very end and the debugger reveals the following error: ** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMutableData InitString]: unrecognized selector sent to instance 0x6b1fe20'*

Furthermore, it says that "NewFreeze" is not of type CFString. Does anybody have a clue what's going on? I really want to save my Objects like that.

回答1:

The problem is that storing custom classes as a node in the Settings plist (NSUserDefaults) is not allowed, since the data is stored in the file system rather than an object from the application. The settings app (where this data will also be visible) has no idea what a "Freeze" object is. The best way to handle what you want to do is to probably use Core Data.

also of note: the error you are getting is caused at the end of the method when you try to initalize a new Freeze object from your runes array, because when you put the object into the runes array you encapsulated it as a NSData object first, but when you get it out you don't dearchive it before setting it to NewFreeze



回答2:

You certainly can save your own objects using the NSKeyedArchiver class like you want to. But:

- (id)initWithCoder:(NSCoder *)decoder
{
    //self = [[Freeze alloc] init]; // I don't think you want to
                                    // allocate a new object here,
    self = [super init];
    if( self != nil )
    {
        //decode properties, other class vars
        self.name = [decoder decodeObjectForKey:@"name"];

    }
    return self;
}

And like it was mentioned before, you can't save it to NSUserDefaults. You can save it to a file in the application documents directory, though.

EDIT: here is some code that I wrote about a year ago as a test. I think this might be very close to what your trying to do. There's some stuff there that is not related, like loading a plist from the main bundle, but I'll leave it in just in case someone might find it useful.

HighScoresManager.h

#import <Foundation/Foundation.h>

@interface ScoreEntry : NSObject <NSCoding> 
{
    NSString *player;
    NSInteger score;
}
@property (nonatomic, retain) NSString *player;
@property (nonatomic, assign) NSInteger score;

+ (ScoreEntry *) entryWithScore:(NSInteger) score andPlayer:(NSString *)player;

@end


@interface HighScoresManager : NSObject
{
    NSMutableArray *m_Highscores;
}
@property (nonatomic, readonly) NSArray *highscores;

- (void) addScore: (NSInteger) score forPlayer:(NSString *)player;
- (void) addScoreEntry: (ScoreEntry *) entry;

@end

HighScoresManager.m

#import "HighScoresManager.h"

@implementation ScoreEntry
@synthesize player;
@synthesize score;

+ (ScoreEntry *) entryWithScore:(NSInteger) score andPlayer:(NSString *)player
{
    ScoreEntry *entry = [[[ScoreEntry alloc] init] autorelease];
    entry.score = score;
    entry.player = player;

    return entry;
}

+ (ScoreEntry *) entryWithNSDictionary:(NSDictionary *)dict
{
    ScoreEntry *entry = [[[ScoreEntry alloc] init] autorelease];
    entry.score = [[dict objectForKey:@"score"] intValue];
    entry.player = [dict objectForKey:@"player"];

    return entry;
}

- (void) encodeWithCoder: (NSCoder *) coder
{
    [coder encodeInteger:score forKey:@"score"];
    [coder encodeObject:player forKey:@"player"];
}

- (id) initWithCoder: (NSCoder *) decoder
{
    if( self = [super init] )
    {
        score = [decoder decodeIntForKey:@"score"];
        player = [[decoder decodeObjectForKey:@"player"] retain];
}

    return (self);
}

- (void) dealloc
{
    [player release];

    [super dealloc];
}

@end


@implementation HighScoresManager

- (BOOL) saveHighScores
{
    BOOL success = NO;

    if( m_Highscores )
    {
        NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);

        NSString *path = [[[pathList objectAtIndex:0] stringByAppendingPathComponent:@"highscores.txt"] stringByExpandingTildeInPath];

        success = [NSKeyedArchiver archiveRootObject:m_Highscores toFile:path];
    }

    return success;
}

- (BOOL) loadHighScores
{
    BOOL success = NO;

    NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);

    NSString *path = [[[pathList objectAtIndex:0] stringByAppendingPathComponent:@"highscores.txt"] stringByExpandingTildeInPath];

    if(m_Highscores)
    {
        [m_Highscores release];
    }

    m_Highscores = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    if( m_Highscores )
    {
        [m_Highscores retain];
        success = YES;
    }
    else
    {
        m_Highscores = [[NSMutableArray alloc] init];
        success = NO;
    }

    // if empty, load default highscores from plist
    if([m_Highscores count] == 0)
    {
        NSString *resourcePath = [[NSBundle mainBundle] resourcePath];

        NSString *path = [[resourcePath stringByAppendingPathComponent:@"DefaultHighscores.plist"] stringByExpandingTildeInPath];

        NSDictionary *defaultHighscoresDict = [[NSDictionary alloc] initWithContentsOfFile:path];

        NSArray *defaultHighscores = [defaultHighscoresDict objectForKey:@"highscores"];

        for (NSDictionary *dict in defaultHighscores)
        {
            [m_Highscores addObject:[ScoreEntry entryWithNSDictionary:dict]];
        }

        [defaultHighscoresDict release];
    }

    return success;
}

- (id) init
{
    self = [super init];
    if(self)
    {
        [self loadHighScores];
    }
    return self;
}

- (NSArray *) highscores
{
    return (NSArray *) m_Highscores;
}

- (void) addScore: (NSInteger) score forPlayer:(NSString *)player
{
    [self addScoreEntry:[ScoreEntry entryWithScore:score andPlayer:player]];
}

- (void) addScoreEntry: (ScoreEntry *) entry
{
    [m_Highscores addObject:entry];
}

- (void) dealloc
{
    [self saveHighScores];
    [m_Highscores release];

    [super dealloc];
}

@end

Maybe I should have just uploaded the files somewhere and linked them here... anyway, it's done now.