【IOS 开发学习总结-OC-37】★文件 I/O——对象归档

时间:2022-02-15 16:21:01

何为对象归档?

对象归档——就是用某种格式把一个或多个对象保存到指定文件中,方便以后从文件中恢复它们。类似于其他语言中的序列化机制。
归档包括2方面的操作:1,将对象写入指定文件;2,从文件中恢复这些对象。

使用NSKeyedArchiver&NSKeyedUnarchiver归档和解档

使用NSKeyedArchiver将对象进行归档,若要恢复这些对象,要用NSKeyedUnarchiver。
NSKeyedArchiver会创建一种所谓带键(keyed) 的档案,这种归档格式中,无论归档哪个对象,都要为该对象分配一个 key, 当程序进行恢复这些对象时,也需要根据 key进行检索。

NSKeyedArchiver&NSKeyedUnarchiver的用法:

用法有如下2种方式:

  1. 归档时:直接调用NSKeyedArchiver的+ (NSData *)archivedDataWithRootObject:(id)rootObject;+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;
    类方法将指定对象作为root 进行归档;
    解档恢复时:调用NSKeyedUnarchiver 的+ (nullable id)unarchiveObjectWithData:(NSData *)data;+ (nullable id)unarchiveObjectWithFile:(NSString *)path;类方法。
  2. 归档时: 以NSMutableData对象作为参数,创建NSKeyedArchiver对象,然后重复调用NSKeyedArchiver对象的- (void)encodeObject:(nullable id)objv forKey:(NSString *)key;此类方法依次归档不同的对象,最后调用- (void)finishEncoding;方法结束归档;
    解档恢复时:依然以NSMutableData对象作为参数,创建NSKeyedUnarchiver对象,然后重复调用- (nullable id)decodeObjectForKey:(NSString *)key;方法依次恢复不同的对象。

第一种方式示例代码:
归档操作:

#import <Foundation/Foundation.h>

int main(int argc , char * argv[])
{
@autoreleasepool{
// 直接使用多个value,key的形式创建NSDictionary对象
NSDictionary* dict = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:89] , @"Objective-C",
[NSNumber numberWithInt:69] , @"Ruby",
[NSNumber numberWithInt:75] , @"Python",
[NSNumber numberWithInt:109] , @"Perl", nil];
// 对dict对象进行归档
[NSKeyedArchiver archiveRootObject:dict
toFile:@"myDict.archive"];
}
}

解档恢复操作:

#import <Foundation/Foundation.h>

int main(int argc , char * argv[])
{
@autoreleasepool{
// 从myDict.archive文件中恢复对象
NSDictionary* dict = [NSKeyedUnarchiver
unarchiveObjectWithFile:@"myDict.archive"];
// 下面代码只是获取NSDictionary中key-value数据
NSLog(@"Objective-C对应的value:%@" ,
[dict valueForKey:@"Objective-C"]);
NSLog(@"Ruby对应的value:%@" ,
[dict valueForKey:@"Ruby"]);
NSLog(@"Python对应的value:%@" ,
[dict valueForKey:@"Python"]);
NSLog(@"Perl对应的value:%@" ,
[dict valueForKey:@"Perl"]);
}
}

代码说明:这种方式简单易用,但功能简单。注意:这里归档的myDict.archive文件(文件名可以任意指定)。

第二种归档/解档方式

跳转到:使用 NSData 完成自定义归档

实现 NSCoding协议

NSKeyedArchiver和NSKeyedUnarchiver这2个类,能否归档任意自定义的 OC 对象呢?
如果我们用NSKeyedArchiver将一个自定义的 FKApple 对象归档到指定文件中,运行会报错如下:
【IOS 开发学习总结-OC-37】★文件 I/O——对象归档
从报错信息中,我们可以看出,我们归档自定义对象时,系统会自动调用自定义对象的encodeWithCoder: 方法进行归档,类似的在解档恢复操作时,会调用该对象的decodeWithCoder: 方法 进行恢复。

那么问题来了:我们怎么实现对任意对象的归档呢?
如果程序需要归档,恢复任意自定义类的实例,那么该类需要实现NSCoding协议,实现该协议就必须实现该协议中定义的如下2个方法:

  1. initWithCoder:——负责恢复该对象;
  2. encodeWithCoder: ——负责归档该对象

一般来说,如果该对象 得到实例变量是 objective-c 类型 ,并且实现了NSCoding协议,就可以直接调用(void)encodeObject:(nullable id)objv forKey:(NSString *)key;方法来归档该实例变量,使用
- (nullable id)decodeObjectForKey:(NSString *)key;方法恢复该实例变量即可。

C 语言的基本数据类型如何归档和解档恢复?

对C语言的数据类型(如整型或浮点型),可用如下 表中的方法进行归档或恢复。
【IOS 开发学习总结-OC-37】★文件 I/O——对象归档

示例代码:
FKApple.h

#import <Foundation/Foundation.h>

// 定义FKApple类,实现NSCoding协议
@interface FKApple : NSObject <NSCoding>
@property (nonatomic , copy) NSString* color;
@property (nonatomic , assign) double weight;
@property (nonatomic , assign) int size;
- (id) initWithColor: (NSString*) color
weight: (double) weight size: (int) size;
@end

FKApple.m

#import "FKApple.h"

@implementation FKApple
@synthesize color = _color;
@synthesize weight = _weight;
@synthesize size = _size;
- (id) initWithColor: (NSString*) color
weight:(double) weight size:(int) size
{
if(self = [super init])
{
self.color = color;
self.weight = weight;
self.size = size;
}
return self;
}
// 重写父类的decription方法
- (NSString*) description
{
// 返回一个字符串
return [NSString stringWithFormat:
@"<FKApple[_color=%@, _weight=%g, _size=%d]>"
, self.color , self.weight , self.size];
}
- (void) encodeWithCoder: (NSCoder*) coder
{
// 调用NSCoder的方法归档该对象的每个实例变量
[coder encodeObject:_color forKey:@"color"];
[coder encodeDouble:_weight forKey:@"weight"];
[coder encodeInt:_size forKey:@"size"];
}
- (id) initWithCoder: (NSCoder*) coder
{
// 使用NSCoder依次恢复color、weight、size这3个key
// 所对应的value,并将恢复的value赋给当前对象的3个实例变量
_color = [coder decodeObjectForKey:@"color"];
_weight = [coder decodeDoubleForKey:@"weight"];
_size = [coder decodeIntForKey:@"size"];
return self;
}
@end

ArchiverApple.m

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建FKApple对象
FKApple* apple = [[FKApple alloc]
initWithColor:@"红色"
weight:3.4
size:20];
// 对apple对象进行归档
[NSKeyedArchiver archiveRootObject:apple
toFile:@"apple.archive"];
}
}

另外,解档恢复的.m 文件。

UnarchiverApple.m

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
@autoreleasepool{
// 从归档文件中恢复对象
FKApple* apple = [NSKeyedUnarchiver
unarchiveObjectWithFile:@"apple.archive"];
// 获取对象的属性
NSLog(@"苹果的颜色为:%@" , apple.color);
NSLog(@"苹果的重量为:%g" , apple.weight);
NSLog(@"苹果的规格为:%d" , apple.size);
}
}

代码说明:
上面的代码中实现了NSCoding协议,使用NSKeyedArchiver归档普通 OC 对象与归档 NSDictionary 对象的方式一样。解档恢复方式也是一样的。
 

使用 NSData 完成自定义归档

如果我们希望一次性收集多个对象,并将这些对象归档到单个档案文件中,此时我们需要借助NSMutableData 来创建NSKeyedArchiver或NSKeyedUnarchiver 对象。

程序一次性将多个对象归档到单个档案文件中的步骤

步骤如下:

  1. 以NSMutableData为参数,创建NSKeyedArchiver对象;
  2. 重复调用NSKeyedArchiver对象的- (void)encodeObject:(nullable id)objv forKey:(NSString *)key;此类方法,用来归档所有需要归档到 一个文件中的对象。
  3. 结束归档。——调用- (void)finishEncoding;方法结束归档;
  4. 根据需要,可以选择将保存归档 数据的 NSData 通过网络传输或输出到磁盘文件上。

示例代码(一次性归档多个 对象):

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
@autoreleasepool{
// 直接使用多个value,key的形式创建NSDictionary对象
NSDictionary* dict = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:89] , @"Objective-C",
[NSNumber numberWithInt:69] , @"Ruby",
[NSNumber numberWithInt:75] , @"Python",
[NSNumber numberWithInt:109] , @"Perl", nil];
// 创建一个NSSet对象
NSSet* set = [NSSet setWithObjects:
@"疯狂iOS讲义",
@"疯狂Android讲义",
@"疯狂Ajax讲义", nil];
// 创建FKApple对象
FKApple* apple = [[FKApple alloc]
initWithColor:@"红色"
weight:3.4
size:20];
// 创建一个NSMutableData对象,用于保存归档数据
NSMutableData* data = [NSMutableData data];
// 以NSMutableData对象作为参数,创建NSKeyedArchiver对象
NSKeyedArchiver* arch = [[NSKeyedArchiver alloc]
initForWritingWithMutableData:data];
// 重复调用encodeObject:forKey:方法归档所有需要归档的对象
[arch encodeObject:dict forKey:@"myDict"];
[arch encodeObject:set forKey:@"set"];
[arch encodeObject:apple forKey:@"myApp"];
// 结束归档
[arch finishEncoding];
// 程序将NSData缓存区保存的数据写入文件
if([data writeToFile:@"multi.archive" atomically:YES] == NO)
{
NSLog(@"归档失败!");
}
}
}
#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
@autoreleasepool{
// 直接使用多个value,key的形式创建NSDictionary对象
NSDictionary* dict = [NSDictionary
dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:89] , @"Objective-C",
[NSNumber numberWithInt:69] , @"Ruby",
[NSNumber numberWithInt:75] , @"Python",
[NSNumber numberWithInt:109] , @"Perl", nil];
// 创建一个NSSet对象
NSSet* set = [NSSet setWithObjects:
@"疯狂iOS讲义",
@"疯狂Android讲义",
@"疯狂Ajax讲义", nil];
// 创建FKApple对象
FKApple* apple = [[FKApple alloc]
initWithColor:@"红色"
weight:3.4
size:20];
// 创建一个NSMutableData对象,用于保存归档数据
NSMutableData* data = [NSMutableData data];
// 以NSMutableData对象作为参数,创建NSKeyedArchiver对象
NSKeyedArchiver* arch = [[NSKeyedArchiver alloc]
initForWritingWithMutableData:data];
// 重复调用encodeObject:forKey:方法归档所有需要归档的对象
[arch encodeObject:dict forKey:@"myDict"];
[arch encodeObject:set forKey:@"set"];
[arch encodeObject:apple forKey:@"myApp"];
// 结束归档
[arch finishEncoding];
// 程序将NSData缓存区保存的数据写入文件
if([data writeToFile:@"multi.archive" atomically:YES] == NO)
{
NSLog(@"归档失败!");
}
}
}

从一个文档中 恢复多个归档对象的步骤(与 归档相反)如下:

  1. 以 NSData 作为参数,创建NSKeyedUnarchiver对象;
  2. 重复调用NSKeyedUnarchiver对象的- (nullable id)decodeObjectForKey:(NSString *)key;这类方法从一个文件中恢复所有归档过的对象;
  3. 调用NSKeyedUnarchiver对象的finishDecoding方法结束恢复。

示例代码:

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建一个NSData对象,用于读取指定文件中的归档数据
NSData* data = [NSData
dataWithContentsOfFile:@"multi.archive"];
// 以NSData对象作为参数,创建NSKeyedUnarchiver对象
NSKeyedUnarchiver* unarch = [[NSKeyedUnarchiver alloc]
initForReadingWithData:data];
// 重复调用decodeObjectForKey:方法恢复所有需要恢复的对象
NSDictionary* dict = [unarch decodeObjectForKey:@"myDict"];
NSSet* set = [unarch decodeObjectForKey:@"set"];
FKApple* myApp = [unarch decodeObjectForKey:@"myApp"];
// 结束恢复
[unarch finishDecoding];
// 下面代码仅仅只是验证恢复是否成功
NSLog(@"%@" , dict);
NSLog(@"%@" , set);
NSLog(@"%@" , myApp);
}
}

这里特别提一嗓子,借助 NSData 的支持,NSKeyedUnarchiver可以一次性将多个对象归档到NSMutableData对象中。由于NSData和NSMutableData代表数据的缓冲区,接下来程序就可以将相应的数据写入网络传输,或者写入底层文件。

使用归档实现深复制

归档会将整个对象转换为字节数据。——特别是,包括这个对象的所有实例变量,如果该实例变量指向另一个 OC 对象,归档时也 会归档该实例变量所指向的 OC对象。
这就是说,当程序归档一个对象时,系统会把该对象关联的所有数据都转为字节数据。如果从这些字节数据中恢复对象,恢复出来的对象与原对象完全相同,,但没有任何共用部分——这就实现了深复制

示例代码:

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
@autoreleasepool{
// 直接使用多个value,key的形式创建NSDictionary对象
NSDictionary* dict = [NSDictionary
dictionaryWithObjectsAndKeys:
[[FKApple alloc]
initWithColor:@"红色"
weight:3.4
size:20] , @"one",
[[FKApple alloc]
initWithColor:@"绿色"
weight:2.8
size:14] , @"two", nil];
// 归档对象,将归档对象的数据写入NSData中
NSData* data = [NSKeyedArchiver
archivedDataWithRootObject:dict];
// 从NSData对象中恢复对象,这样即可完成深复制
NSDictionary* dictCopy = [NSKeyedUnarchiver
unarchiveObjectWithData:data];
// 获取复制的NSDictionary对象中key为one对应的FKApple对象
FKApple* app = [dictCopy objectForKey:@"one"];
// 修改该FKApple对象的color。
[app setColor:@"紫色"];
// 获取原始的NSDictionary对象中key为one对应的FKApple对象
FKApple* oneApp = [dict objectForKey:@"one"];
// 访问该FKApple的颜色,程序将会发现该颜色没有任何改变
NSLog(@"dict中key为one对应苹果的颜色为:%@" ,
oneApp.color);
}
}