如何将包含其他数组的NSMutableArray保存到文件中。

3
这个问题以前被问过,人们已经提供了非常好的指导,例如这里
然而,我想知道如果我只是想将一个NSMutableArray(包含另一个NSMutableArray的各种实例)保存到文件中,是否真的需要使用NSCoder?我尝试过,但只得到一个错误消息:
    -(void)saveLibraryDat {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"myLibrary.dat"];
    NSError *error;

    [myLibrary writeToFile:filePath atomically:YES];

    if (error) {
        NSLog(@"There was an error saving myLibrary.dat: %@", error);
    }

}

我的错误信息:

2011-05-13 22:00:47.840 MoleNotes[15437:207] There was an error saving myLibrary.dat: (
    1,
    2
)

那么我猜我需要使用NSCoder,对吧?如果是这样的话,我想知道该怎么做。有人已经解释了如何使用类来实现,但在我的情况下,我有一个包含各种类实例的NSMutableArray(myLibrary)。我是否需要在这个类和NSMutableArray中实现NSCoder呢?

我像这样分配我的库:

    myLibrary = [[NSMutableArray alloc] init];

然后像这样添加一个名为NoteBook.m的类的实例:
    NoteBook *newNoteBook = [[NoteBook alloc] init];
       newNoteBook.titleName = @"Notes"; // etc.

[myLibrary addObject:newNoteBook];

那么,我应该把NSCoder命令放在哪里?只需要放在我的NoteBook.m类中吗?这样会自动处理myLibrary吗?
感谢任何建议。
编辑:
所以我已经更新了代码,但是我想关键问题是我的NSMutableArray myLibrary 包含多个我设置的自定义类(称为notebook)的实例。我已经为这个类(及其所有变量)设置了NSCoding,以便可以保存和加载它。
现在,如果我在应用程序中创建NSMutableArray(即当首次启动应用程序时,不存在文件),而不是从磁盘加载它,则我的应用程序完全正常运行:
    -(void) setupLibrary {

    myLibrary = [[NSMutableArray alloc] init];

    NoteBook *newNoteBook = [[NoteBook alloc] init];

    newNoteBook.titleName = @"Notes"; 
/...

如果我从磁盘加载它,它也能正常工作:
-(void)loadLibraryDat {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"myLibrary.dat"];

    myLibrary = [[NSMutableArray alloc] init];  
    myLibrary = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];

    if (!myLibrary) {
        // if it couldn't be loaded from disk create a new one
        NSLog(@"myLibrary.dat empty... set up new one");


        [self setupLibrary];
        } else { NSLog(@"Loading myLibrary.dat successful."); }

    }

如果我在加载库后记录其中的所有内容,一切仍然正常。例如,以下内容完全正常:
    NSLog(@"%@", [[self.myLibrary objectAtIndex:0] titleName]); 

然而,一个大问题是如果任何其他方法试图访问myLibrary。例如,如果我从另一个方法调用相同的日志命令,应用程序将崩溃并显示以下错误消息:

    [NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510
2011-05-14 14:09:10.490 Notes[17091:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString objectAtIndex:]: unrecognized selector sent to instance 0x4b38510'

这对我来说听起来像是我的Library已经被释放了,但我不知道为什么。这可能是怎么发生的?我有一种感觉,我在NSCoding设置中做错了什么...因为如果我只是在代码中创建myLibrary,一切都很好。只有当我从磁盘加载它时,应用程序才会崩溃。
这是类的设置:
  #import <Foundation/Foundation.h>

@interface NoteBook : NSObject <NSCoding> {

    NSString *titleName;

    NSString *fileName;
    NSMutableArray *tabTitles;
    NSMutableArray *tabColours;
    NSMutableArray *tabReference;   

}

@property (nonatomic, retain) NSString *titleName;

@property (nonatomic, retain) NSString *fileName;
@property (nonatomic, retain) NSMutableArray *tabTitles;
@property (nonatomic, retain) NSMutableArray *tabColours;
@property (nonatomic, retain) NSMutableArray *tabReference;

-(id)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;


@end


//
//  NoteBook.m

#import "NoteBook.h"


@implementation NoteBook

@synthesize titleName, fileName, tabTitles, tabColours, tabReference;

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        self.titleName = [aDecoder decodeObjectForKey:@"titleName"];
        self.fileName = [aDecoder decodeObjectForKey:@"fileName"];
        self.tabTitles = [aDecoder decodeObjectForKey:@"tabTitles"];
        self.tabColours = [aDecoder decodeObjectForKey:@"tabColours"];
        self.tabReference = [aDecoder decodeObjectForKey:@"tabReference"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:titleName forKey:@"titleName"];
    [aCoder encodeObject:fileName forKey:@"fileName"];
    [aCoder encodeObject:tabTitles forKey:@"tabTitles"];
    [aCoder encodeObject:tabColours forKey:@"tabColours"];
    [aCoder encodeObject:tabReference forKey:@"tabReference"];
}

@end

编辑:

我想我解决了它......我忘了一个小的“self”......这搞砸了一切并且取消了我的库:

    self.myLibrary = [NSKeyedUnarchiver
                   unarchiveObjectWithFile:filePath];

if (self.myLibrary == nil) {
    NSLog(@"myLibrary.dat empty... set up new one");
    [self setupLibrary];
} else { NSLog(@"Loading myLibrary.dat successful."); }

我认为你应该考虑使用XML plists或JSon,但我承认我不记得NSCoder的作用,只是我从来没有用它来保存我的NSArrays。就像[array writeToFile:...]这样的东西。 - gurghet
1
只要您的对象符合plist规范(即它们是字符串、数组、数字等),writeToFile就可以正常工作。如果要存储自定义类,则必须使用NSCoder并自行编组。 - Martin Wickman
@Martin Wickman。谢谢,我已经尝试实现了这个,但是我想我一定走错了地方。加载似乎是有效的,但是如果我尝试访问我的变量,应用程序就会崩溃...请查看我编辑过的帖子。非常感谢您的帮助! - n.evermind
@Martin Wickman:抱歉打扰了,我已经自己解决了。谢谢! - n.evermind
2个回答

5
你的代码出了问题。变量"error"没有初始化也没有被设置,因此在你检查时,你只能看到随机的垃圾数据。如果你想知道写入是否成功,请检查writeToFile:atomically:的返回值。如果写入成功,它将是YES,否则就是NO
然而,NSArray的writeTo…方法是用于创建属性列表的。如果在你的数组中有非属性列表对象,那么这个方法就不适合使用,你需要使用归档器。只需执行类似于[[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:writeToFile:filePath atomically:YES]的操作即可。
要使你的对象正确地符合NSCoding,只需让它们实现initWithCoder:encodeWithCoder:方法,并在这些方法中,使用NSCoder的存储方法来存储对象的实例变量(以及检索方法来取回它们)。

我尝试了以下代码:`BOOL succeed = [myLibrary writeToFile:filePath atomically:YES]; if (!succeed){ NSLog(@"There was an error saving myLibrary.dat: %@", error); }` 但是返回了 (nil)... 我想我不太明白如何检查错误,感到有些担心。 - n.evermind
谢谢,我已经完成了所有这些工作(包括NSCoder的实现),但我仍然在错误检查中得到(null):BOOL succeed = [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:filePath atomically:YES]; if (!succeed){ NSLog(@"There was an error saving myLibrary.dat: %@", error); } - n.evermind
好的,我尝试实现了你建议的所有内容,但是如果我尝试访问我尝试保存的NSMutableArray,我的应用程序会崩溃,我认为我仍然做错了什么。请查看我上面更新的代码。非常感谢你的帮助! - n.evermind

1

NSCoder 是一个协议,你的类必须符合它才能被归档到数据/文件中。类似于 Java 中的 Serealizabe

像这样在类头中添加符合性:

@interface NoteBook : NSObject <NSCoder> { // …

然后你必须实现两个方法:

-(id)initWithCoder:(NSCoder)decoder;
{
    self = [super initWithCoder:decoder];
    if (self) {
        _someIvar = [decoder decodeObjectForKey:@"someKey"];
        // And more init as needed…
    }
    return self;
}
-(void)encodeWithCoder:(NSCoder)coder;
{
   [super encodeWithCoder:coder];
   [coder encodeObject:_someIvar forKey@"someKey"];
   /// Etc…
}

我也建议不要使用-[NSArray writeToFile:atomically:],因为它只适用于属性列表兼容对象,而不是编码兼容类。属性列表对象包括NSStringNSDataNSArrayNSDictionaryNSDateNSNumber。该列表无法扩展。

相反,使用NSKeyedArchiver/NSKeyedUnarchiver。使用起来几乎一样简单:

if (![NSKeyedArchive archiveRootObject:yourArrat toFile:path]) {
  // It failed.
}

好的,我尝试实现了你建议的所有内容,但是如果我尝试访问我尝试保存的NSMutableArray,我的应用程序会崩溃,我认为我仍然做错了什么。请查看我上面更新的代码。非常感谢你的帮助! - n.evermind

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接