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

Posted

技术标签:

【中文标题】如何将 NSMutableArray(包含其他数组)保存到文件【英文标题】:How to save a NSMutableArray (containing other arrays) to file 【发布时间】:2011-05-14 08:33:23 【问题描述】:

以前有人问过这个问题,人们对如何做到这一点给出了很好的指导,例如here.

但是,如果我只是想将一个 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."); 

【问题讨论】:

我认为你应该考虑 XML plist 或 JSon,但我承认我不记得 NSCoder 是做什么的,只是我从来没有用它来保存我的 NSArrays。类似于 [array writeToFile:.... writeToFile 只要您的对象符合 plist 标准(即它们是字符串、数组、数字等),就应该可以工作。要存储自定义类,您必须使用 NSCoder 并自己编组。 @Martin Wickman。谢谢,我已经尝试过实现这个,但我想我一定是在某个地方完全错了。加载似乎有效,但如果我尝试访问我的变量,应用程序将崩溃......请参阅我编辑的帖子。非常感谢您的帮助! @Martin Wickman:很抱歉打扰了,我自己解决了。谢谢! 【参考方案1】:

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

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

要使您的对象正确符合 NSCoding,只需让它们实现 initWithCoder:encodeWithCoder:,在这些方法中,使用 NSCoder's storage methods 来存储对象的实例变量(以及将它们取出的检索方法) .

【讨论】:

我试过BOOL succeed = [myLibrary writeToFile:filePath atomically:YES]; if (!succeed) NSLog(@"There was an error saving myLibrary.dat: %@", error); ,但返回了(nil)......我想我不明白检查错误的全部想法,恐怕。 谢谢,我做了所有这些(包括 NSCoder 的实现),但我的错误检查仍然得到 (null):BOOL succeed = [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:filePath atomically:YES]; if (!succeed) NSLog(@"There was an error saving myLibrary.dat: %@", error); 好的,我已尝试实现您建议的所有内容,但我认为我仍然做错了,因为如果我尝试访问我尝试保存的 NSMutableArray,我的应用程序会崩溃。请参阅上面的更新代码。非常感谢您的帮助!【参考方案2】:

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,我的应用程序会崩溃。请参阅上面的更新代码。非常感谢您的帮助!

以上是关于如何将 NSMutableArray(包含其他数组)保存到文件的主要内容,如果未能解决你的问题,请参考以下文章

如何将自定义对象保存在包含更多自定义对象的数组中

iOS:将两个 NSMutableArray 存储在 .plist 文件中

如何将 NSMutableArray 转换为 Swift 数组?

如何按数值对包含 NSDictionary 的 NSMutableArray 进行排序?

在 Xcode 中保存 NSMutableArray 的其他方法

如何将多个 NSIndexPath 添加到 NSMutableArray