如何将 NSMutableArray (包含其他数组)保存到文件

发布于 2024-11-07 16:38:16 字数 5287 浏览 0 评论 0原文

之前已经有人问过这个问题,人们已经就如何做到这一点给出了非常好的说明,例如 这里

但是,我想知道如果我只是想将一个 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),其中包含类的各种实例。我必须在这个类中实现 NSCoder 和 NSMutableArray 吗?

我像这样分配我的库:

    myLibrary = [[NSMutableArray alloc] init];

然后像这样添加一个名为 NoteBook.m 的类的实例:

    NoteBook *newNoteBook = [[NoteBook alloc] init];
       newNoteBook.titleName = @"Notes"; // etc.

[myLibrary addObject:newNoteBook];

那么我到底应该把 NSCoder 命令放在哪里呢?只进入我的 NoteBook.m 课程?这会自动处理 myLibrary 吗?

感谢您的任何建议。


编辑:

所以我更新了我的代码,但我想最大的问题是我的 NSMutableArray myLibrary 包含我设置的自定义类(称为笔记本)的多个实例。我已经为这个类(及其所有变量)设置了 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'

在我看来,这听起来好像 myLibrary 已以某种方式释放,但我不明白为什么。怎么会发生这种事?我有一种感觉,我在 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

编辑:

我想我已经解决了它...我忘记了一点“自我”...这把一切搞砸了并释放了 myLibrary:

    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."); }

This has been asked before and people have given very good instructions on how to do this, e.g. here.

However, I was wondering if I really need to work with NSCoder if I simply wanted to save one NSMutableArray (containing various instances of another NSMutableArray) to a file? I tried this but only got an error message:

    -(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);
    }

}

My error message:

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

So I guess I have to work with NSCoder, right? If so, I was wondering how to go about this. People have explained how to do this with a class, but in my case, I have a NSMutableArray (myLibrary) which contains various instances of a class. Will I have to implement the NSCoder in this class and the NSMutableArray?

I alloc my library like this:

    myLibrary = [[NSMutableArray alloc] init];

And then add instances of a class called NoteBook.m like this:

    NoteBook *newNoteBook = [[NoteBook alloc] init];
       newNoteBook.titleName = @"Notes"; // etc.

[myLibrary addObject:newNoteBook];

So where exactly do I put the NSCoder commands? Only into my NoteBook.m class? Will this automatically take care of myLibrary?

Thanks for any suggestions.


EDIT:

So I've updated my code, but I guess the big problem is that my NSMutableArray myLibrary contains several instances of a custom class I've set up (called notebook). I have set up NSCoding for this class (and all its variables) so that I can save it and load it.

Now my app works totally fine if I create the NSMutableArray in the app (i.e. when the app is started for the very first time, no file exists), instead of loading it from disk:

    -(void) setupLibrary {

    myLibrary = [[NSMutableArray alloc] init];

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

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

If I load it from disk, it works fine as well:

-(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."); }

    }

If I log everything which is contained in my library after loading it, everything is still fine. E.g. the following works totally fine:

    NSLog(@"%@", [[self.myLibrary objectAtIndex:0] titleName]); 

The big problem is, however, if any other method tries to access myLibrary. For instance, if I call the very same log command from another method, the app will crash and I get this error message:

    [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'

This sounds to me as if myLibrary has become deallocated somehow, but I can't see why. How could this have happened? I have the feeling that I did something wrong in my NSCoding set up... because if I simply create myLibrary in code, everything works like wonderfully. It's only if I load it from the disk, that the app will crash.


Here is the class setup:

  #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

EDIT:

I think I've solved it... I forgot a little 'self'... which messed it all up and deallocated myLibrary:

    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."); }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

无法回应 2024-11-14 16:38:16

你的代码被破坏了。 “error”变量未初始化且从未设置,因此当您检查它时,您只是看到随机垃圾数据。如果想知道写入是否成功,可以查看writeToFile:atomically:的返回值。如果写入成功,则为 YES;如果未成功,则为 NO

然而,NSArray 的 writeTo... 方法用于创建 plist。如果数组中存在非属性列表对象,则该方法不合适,而归档器正是您想要的。只需执行类似 [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:writeToFile:filePathatomically:YES] 的操作即可。

要使您的对象正确符合 NSCoding,只需让它们实现 initWithCoder:encodeWithCoder:,并在这些方法中使用 NSCoder 的存储方法 用于存储对象的实例变量(以及将它们取回的检索方法)。

Your code is busted. The "error" variable is uninitialized and never set, so when you check it, you're just seeing random garbage data. If you want to know whether the write was successful, check the return value of writeToFile:atomically:. It will be YES if the write succeeded and NO if it didn't.

However, NSArray's writeTo… methods are for creating plists. If non-property-list objects are in your array, that method isn't appropriate, and an archiver is what you want. Just do something like [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:writeToFile:filePath atomically:YES].

To make your objects conform to NSCoding correctly, just have them implement initWithCoder: and encodeWithCoder:, and in those methods, use NSCoder's storage methods to store the object's instance variables (and the retrieval methods to get them back out).

寻找一个思念的角度 2024-11-14 16:38:16

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:] 因为仅使用符合属性列表的对象,而不是编码合规类。属性列表对象是 NSStringNSDataNSArrayNSDictionaryNSDate ,和NSNumber。该列表无法扩展。

请改用 NSKeyedArchiver/NSKeyedUnarchiver。使用起来几乎一样简单:

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

NSCoder is a protocol that your class must conform to in order to be archived to data/file. Works something like Serealizabe in Java.

Add conformance to the class header like this:

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

And then you must implement two methods:

-(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…
}

I would also advice against using -[NSArray writeToFile:atomically:] since in work with property list compliant objects only, not coding compliant classes. The property list object are NSString, NSData, NSArray, or NSDictionary, NSDate, and NSNumber. The list can not be extended.

Instead use NSKeyedArchiver/NSKeyedUnarchiver. Almost as simple to use:

if (![NSKeyedArchive archiveRootObject:yourArrat toFile:path]) {
  // It failed.
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文