数据持久化(三)使用NSKeyedArchiver归档

归档是一个数据持久化的过程,该过程用某种格式来保存一个或多个对象,以便以后还原这些对象。

可以使用NSKeyedArchiver类创建带键(keyed)的文件来完成。在带键的文件中,每个归档的对象对应一个键,从文件中加载对象时,就是根据这个键来检索对象。


1.归档基本的Foundation对象

使用NSKeyedArchiver可以归档和恢复NSString、NSArray、NSDictionary、NSSet、NSDate、NSNumber和NSData等基本的Foundation对象。代码如下:

- (IBAction)archiveObject:(id)sender {
    
    // 创建Foundation对象
    NSString     *str        = @"NSString Object";
    NSDate       *date       = [NSDate date];
    NSNumber     *number     = [NSNumber numberWithInt:100];
    NSArray      *array      = [NSArray arrayWithObjects:str, date, number, nil];
    NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:@[str, date, number]
                                                           forKeys:@[@"NSString Key", @"NSDate Key", @"NSNumber Key"]];
    
    // 获取文件路径
    NSString *documentPath       = [self getDocumentPath];
    NSString *arrayFilePath      = [documentPath stringByAppendingPathComponent:@"array.plist"];
    NSString *dictionaryFilePath = [documentPath stringByAppendingPathComponent:@"dictionary.plist"];
    
    // 归档数组和字典对象
    [NSKeyedArchiver archiveRootObject:array      toFile:arrayFilePath];
    [NSKeyedArchiver archiveRootObject:dictionary toFile:dictionaryFilePath];
    
}

- (IBAction)unarchiveObject:(id)sender {
    
    // 获取文件路径
    NSString *documentPath       = [self getDocumentPath];
    NSString *arrayFilePath      = [documentPath stringByAppendingPathComponent:@"array.plist"];
    NSString *dictionaryFilePath = [documentPath stringByAppendingPathComponent:@"dictionary.plist"];
    
    // 恢复对象
    NSArray      *array      = [NSKeyedUnarchiver unarchiveObjectWithFile:arrayFilePath];
    NSDictionary *dictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:dictionaryFilePath];
    
    // 输出对象信息
    NSString *dataInfo = [NSString stringWithFormat:@"array = %@\ndictionary = %@", array.description, dictionary.description];
    self.showObjects_textView.text = dataInfo;
    
}

/* 获取Documents文件夹路径 */
- (NSString *)getDocumentPath {
    NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentPath = documents[0];
    return documentPath;
}

点击视图中的Archive按钮,将包含NSString,NSNumber和NSDate对象的数组或字典对象归档到文件中,在Documents目录下可以看到array.plist和dictionary.plist文件:



再点击视图中的Unarchive按钮,首先从文件中恢复数组和字典对象,然后在视图中显示其信息:


要特别注意的是,该方法只能应用于基本的Foundation对象,如果NSArray或NSDictionary中包含自定义的类对象,或直接归档自定义类对象,那么程序将会崩溃:

Book类:

#import <Foundation/Foundation.h>

@interface Book : NSObject

@property (strong, nonatomic) NSString *name;

@property (assign, nonatomic) BOOL published;

@property (assign, nonatomic) float price;

@property (strong, nonatomic) NSMutableArray *info;

@end


创建自定义对象并加入到归档的数组对象中:
    // 创建自定义对象
    Book *book     = [[Book alloc] init];
    book.name      = @"2.0";
    book.published = NO;
    book.price     = 100.0;
    book.info      = [[NSMutableArray alloc] init];
    [book.info addObject:@"Programming in Objective-C Fourth Edition"];
    
    NSArray *array = [NSArray arrayWithObjects:str, date, number, book, nil];


运行时程序将会崩溃:

2014-02-01 14:05:52.157 KeyedArchiver[1126:70b] -[Book encodeWithCoder:]: unrecognized selector sent to instance 0xcb4e560
2014-02-01 14:05:52.161 KeyedArchiver[1126:70b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Book encodeWithCoder:]: unrecognized selector sent to instance 0xcb4e560'

原因是Book类的encodeWithCoder方法没有实现。


2.归档和恢复自定义类对象

对于自定义的类必须要实现<NSCoding>协议中的encodeWithCoder和initWithCoder方法,才能归档和恢复这个类产生的对象。

#import "Book.h"

static NSString *kBookName  = @"BookName";
static NSString *kPublished = @"BookPublished";
static NSString *kPrice     = @"BookPrice";
static NSString *kInfo      = @"BookInfo";

@interface Book () <NSCoding>

@end

@implementation Book

- (NSString *)description {
    return [NSString stringWithFormat:@"BookName = %@ Published = %i Price = %f BookInfo = %@", self.name, self.published, self.price, self.info];
}

#pragma mark - NSCoding Delegate

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.name    forKey:kBookName];
    [aCoder encodeBool:self.published forKey:kPublished];
    [aCoder encodeFloat:self.price    forKey:kPrice];
    [aCoder encodeObject:self.info    forKey:kInfo];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self.name      = [aDecoder decodeObjectForKey:kBookName];
    self.published = [aDecoder decodeBoolForKey:kPublished];
    self.price     = [aDecoder decodeFloatForKey:kPrice];
    self.info      = [aDecoder decodeObjectForKey:kInfo];
    return self;
}

@end

运行程序,先Archive,再Unarchive,结果如下:



如果要归档Book类的子类对象,那么要在encodeWithCoder和decodeWithCoder方法中首先调用父类的编码和解码方法。这里要注意,如果在Book类中<NSCoding>协议声明在匿名类别中,那么协议的方法将是私有的,子类无法访问,因此要将<NSCoding>协议声明移到接口部分:

@interface Book () // <NSCoding>

@end
修改为:
@interface Book : NSObject <NSCoding>

下面新建一个Book类的子类Literature类:

#import "Book.h"

@interface Literature : Book <NSCoding>

@property (copy, nonatomic) NSString *author;

@end
#import "Literature.h"

static NSString *kAuthor = @"LiteratureAuthor";

@implementation Literature

- (NSString *)description {
    NSString *bookDes  = [super description];
    NSString *literDes = [bookDes stringByAppendingFormat:@"Author = %@", self.author];
    return literDes;
}

#pragma mark - NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [super encodeWithCoder:aCoder];
    [aCoder encodeObject:self.author forKey:kAuthor];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    self.author = [aDecoder decodeObjectForKey:kAuthor];
    return self;
}

@end
在archive的action方法中加入Literature类对象,并进行归档:

    // 创建Book子类对象
    Literature *literature = [[Literature alloc] init];
    literature.author = @"Shakespeare";
    literature.name = book.name;
    literature.published = book.published;
    literature.price = book.price;
    literature.info = [[NSMutableArray alloc] initWithArray:[book.info copy]];
    
    NSArray *array = [NSArray arrayWithObjects:str, date, number, book, literature, nil];

运行结果:





注意:

(1)在归档时类的键可以任意指定,为了避免各个类的键出现重名,所以最好在键名前面加上类名前缀,例如:

Book类:

static NSString *kBookName  = @"BookName";
static NSString *kPublished = @"BookPublished";
static NSString *kPrice     = @"BookPrice";
static NSString *kInfo      = @"BookInfo";

Literature类:

static NSString *kAuthor = @"LiteratureAuthor";



(2)archive方法会返回一个BOOL值:

+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;

通过BOOL可以判断归档是否成功。

unarchive方法返回的是对象的引用,可以判断其是否为nil从而保证程序的健壮性:

+ (id)unarchiveObjectWithFile:(NSString *)path;

更加安全的做法是:

    // 归档数组和字典对象
    if (![NSKeyedArchiver archiveRootObject:array toFile:arrayFilePath]) {
        NSLog(@"fail to archive array object");
    }
    if (![NSKeyedArchiver archiveRootObject:dictionary toFile:dictionaryFilePath]) {
        NSLog(@"fail to archive dictionary object");
    }
    // 恢复对象
    NSArray      *array      = [NSKeyedUnarchiver unarchiveObjectWithFile:arrayFilePath];
    NSDictionary *dictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:dictionaryFilePath];
    
    // 输出对象信息
    if (array && dictionary) {
        NSString *dataInfo = [NSString stringWithFormat:@"array = %@\ndictionary = %@", array.description, dictionary.description];
        self.showObjects_textView.text = dataInfo;
    }


3.使用NSData类归档自定义对象

如果我们不希望创建数组或字典等集合来保存对象,并且想单独保存一个或几个对象到同一个文件,那么我们可以先在内存中申请一块内存空间用来保存NSData对象,然后使用NSKeyedArchiver类将这些对象编码成NSData类并保存到内存中,在编码完成后将这些二进制数据写入磁盘的文件中。

在恢复对象时,其过程是相反的。

归档的代码:

    // 创建自定义对象
    Book *book     = [[Book alloc] init];
    book.name      = @"OC 2.0";
    book.published = NO;
    book.price     = 100.0;
    book.info      = [[NSMutableArray alloc] init];
    [book.info addObject:@"Programming in Objective-C Fourth Edition"];
    
    // 创建Book子类对象
    Literature *literature = [[Literature alloc] init];
    literature.author = @"Shakespeare";
    literature.name = book.name;
    literature.published = book.published;
    literature.price = book.price;
    literature.info = [[NSMutableArray alloc] initWithArray:[book.info copy]];
    
    // 将Book类和Literature类编码成二进制数据
    NSMutableData *tempData = [NSMutableData data];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:tempData];
    [archiver encodeObject:book forKey:@"Book"];
    [archiver encodeObject:literature forKey:@"Literature"];
    [archiver finishEncoding];
    
    // 保存编码后的数据
    NSString *documentPath = [self getDocumentPath];
    NSString *dataFilePath = [documentPath stringByAppendingPathComponent:@"data.plist"];
    if (![tempData writeToFile:dataFilePath atomically:YES]) {
        NSLog(@"fail to archive data object");
    }

恢复对象的代码:

    // 获取文件路径
    NSString *documentPath       = [self getDocumentPath];
    NSString *dataFilePath = [documentPath stringByAppendingPathComponent:@"data.plist"];
    
    // 从文件中读取二进制数据
    NSData *tempData = [NSData dataWithContentsOfFile:dataFilePath];
    if (tempData) {
        // 通过解码恢复对象
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:tempData];
        Book *book = [unarchiver decodeObjectForKey:@"Book"];
        Literature *literature = [unarchiver decodeObjectForKey:@"Literature"];
        [unarchiver finishDecoding];
        
        // 显示对象内容
        self.showObjects_textView.text = [NSString stringWithFormat:@"Book = %@, Literature = %@", book, literature];
    }


4.使用NSKeyedArchiver类压缩单个对象

如果只是保存一个对象,可以先将一个对象压缩成二进制数据,调用以下方法:

+ (NSData *)archivedDataWithRootObject:(id)rootObject;

然后用NSData的writeToFile方法即可:

- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;

另外,通过压缩和解压可以实现对象的深复制:

        NSMutableArray *marray1 = [[NSMutableArray alloc] initWithObjects:
                                   [NSMutableString stringWithString:@"1"],
                                   [NSMutableString stringWithString:@"2"],
                                   [NSMutableString stringWithString:@"3"],
                                   nil];
        NSMutableArray *marray2 = nil;
        
        NSData *tempData = [NSKeyedArchiver archivedDataWithRootObject:marray1];
        marray2 = [NSKeyedUnarchiver unarchiveObjectWithData:tempData];
        
        NSMutableString *mstr = marray1[0];
        [mstr appendString:@" append"];
        
        NSLog(@"marray1 = %@", marray1);
        NSLog(@"marray2 = %@", marray2);

2014-02-01 16:22:22.531 DeepCopy[2022:303] marray1 = (
    "1 append",
    2,
    3
)
2014-02-01 16:22:22.533 DeepCopy[2022:303] marray2 = (
    1,
    2,
    3
)

由输出结果可以看到复制的不是对象的引用,而是对象的具体内容。所以对marray1指向的对象的修改不会影响到marray2指向的对象。



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值