何为对象归档?
对象归档——就是用某种格式把一个或多个对象保存到指定文件中,方便以后从文件中恢复它们。类似于其他语言中的序列化机制。
归档包括2方面的操作:1,将对象写入指定文件;2,从文件中恢复这些对象。
使用NSKeyedArchiver&NSKeyedUnarchiver归档和解档
使用NSKeyedArchiver将对象进行归档,若要恢复这些对象,要用NSKeyedUnarchiver。
NSKeyedArchiver会创建一种所谓带键(keyed) 的档案,这种归档格式中,无论归档哪个对象,都要为该对象分配一个 key, 当程序进行恢复这些对象时,也需要根据 key进行检索。
NSKeyedArchiver&NSKeyedUnarchiver的用法:
用法有如下2种方式:
- 归档时:直接调用NSKeyedArchiver的
+ (NSData *)archivedDataWithRootObject:(id)rootObject;
或+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;
类方法将指定对象作为root 进行归档;
解档恢复时:调用NSKeyedUnarchiver 的+ (nullable id)unarchiveObjectWithData:(NSData *)data;
或+ (nullable id)unarchiveObjectWithFile:(NSString *)path;
类方法。- 归档时: 以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文件(文件名可以任意指定)。
第二种归档/解档方式
实现 NSCoding协议
NSKeyedArchiver和NSKeyedUnarchiver这2个类,能否归档任意自定义的 OC 对象呢?
如果我们用NSKeyedArchiver将一个自定义的 FKApple 对象归档到指定文件中,运行会报错如下:
从报错信息中,我们可以看出,我们归档自定义对象时,系统会自动调用自定义对象的encodeWithCoder:
方法进行归档,类似的在解档恢复操作时,会调用该对象的decodeWithCoder:
方法 进行恢复。
那么问题来了:我们怎么实现对任意对象的归档呢?
如果程序需要归档,恢复任意自定义类的实例,那么该类需要实现NSCoding协议,实现该协议就必须实现该协议中定义的如下2个方法:
initWithCoder:
——负责恢复该对象;encodeWithCoder:
——负责归档该对象
一般来说,如果该对象 得到实例变量是 objective-c 类型 ,并且实现了NSCoding协议,就可以直接调用(void)encodeObject:(nullable id)objv forKey:(NSString *)key;
方法来归档该实例变量,使用 - (nullable id)decodeObjectForKey:(NSString *)key;
方法恢复该实例变量即可。
C 语言的基本数据类型如何归档和解档恢复?
对C语言的数据类型(如整型或浮点型),可用如下 表中的方法进行归档或恢复。
示例代码:
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 对象。
程序一次性将多个对象归档到单个档案文件中的步骤
步骤如下:
- 以NSMutableData为参数,创建NSKeyedArchiver对象;
- 重复调用NSKeyedArchiver对象的
- (void)encodeObject:(nullable id)objv forKey:(NSString *)key;
此类方法,用来归档所有需要归档到 一个文件中的对象。- 结束归档。——调用
- (void)finishEncoding;
方法结束归档;- 根据需要,可以选择将保存归档 数据的 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(@"归档失败!");
}
}
}
从一个文档中 恢复多个归档对象的步骤(与 归档相反)如下:
- 以 NSData 作为参数,创建NSKeyedUnarchiver对象;
- 重复调用NSKeyedUnarchiver对象的
- (nullable id)decodeObjectForKey:(NSString *)key;
这类方法从一个文件中恢复所有归档过的对象;- 调用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);
}
}