写在前面
1.归档是将数据持久化的一种方式,一般针对于比较复杂对象,比如自定义的对象,来进行数据持久化操作。
2.归档的对象需要遵循NSCoding协议,存储的时候调用encodeWithCoder:方法,读取的时候调用initWithCoder:方法。
3.将数据写入本地需要调用 [NSKeyedArchiver archiveRootObject:per toFile:filePath],filePath为存储的路径。
4.从本地读取数据需要调用[NSKeyedUnarchiver unarchiveObjectWithFile:filePath],filePath为存储的路径。
对自定义对象进行归档
1.自定义Person类服从NSCoding协议
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface Person : NSObject <NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) CGFloat height;
@end
2.实现协议方法encodeWithCoder:和initWithCoder:
#import "Person.h"
@implementation Person
//写入文件时调用 -- 将需要存储的属性写在里面
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInt:self.age forKey:@"age"];
[aCoder encodeFloat:self.height forKey:@"height"];
}
//从文件中读取时调用 -- 将需要存储的属性写在里面
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntForKey:@"age"];
self.height = [aDecoder decodeFloatForKey:@"height"];
}
return self;
}
@end
3.写入与读取
写入:[NSKeyedArchiver archiveRootObject:per toFile:filePath]
读取: [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self saveModel];
[self readModel];
}
//存储数据
- (void)saveModel {
Person *per = [Person new];
per.name = @"xiaoming";
per.age = 18;
per.height = 180;
NSString *filePath = [self getFilePath];
//将对象per写入
[NSKeyedArchiver archiveRootObject:per toFile:filePath];
}
//读取数据
- (void)readModel {
NSString *filePath = [self getFilePath];
//取出per对象
Person *per = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@ -- %@ -- %d -- %.0f", per, per.name, per.age, per.height);
}
//获得全路径
- (NSString *)getFilePath {
//获取Documents
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//在Documents下创建Person.data文件
NSString *filePath = [doc stringByAppendingPathComponent:@"Person.data"];
return filePath;
}
@end
readModel:方法中打印出存储的per对象相关信息,则对自定义对象Person数据持久化成功
运用runtime优化
当该类拥有上百个属性时,那将会花费更多的功夫在重复代码上,所以使用运行时机制截取类的成员变量,进行赋值
Animal 类
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
//成员变量
@property (nonatomic,assign) double weight;
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
Class c = self.class;
// 截取类和父类的成员变量
while (c && c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(c, &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
// 获得c的父类
c = [c superclass];
free(ivar);
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
Class c = self.class;
// 截取类和父类的成员变量
while (c && c != [NSObject class]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(c, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
c = [c superclass];
// 释放内存
free(ivar);
}
}
代码测试
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CXDog *d = [[CXDog alloc] init];
d.name = @"cx";
d.age = 12;
d.weight = 1.55;
// 将对象归档
[NSKeyedArchiverarchiveRootObject:d toFile:@"/Users/c_xie/Desktop/wj.xxoo"];
// 将对象解档
CXDog *dog = [NSKeyedUnarchiverunarchiveObjectWithFile:@"/Users/c_xie/Desktop/wj.xxoo"];
NSLog(@"%@,%d,%f",dog.name,dog.age,d.weight);
}
runtime自动归档和解档
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSString *address;
@property (nonatomic, strong) NSString *company;
@property (nonatomic, strong) NSString *job;
@end
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init])
{
unsigned int outCount;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++)
{
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
free(ivars)
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int outCount;
Ivar *ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++)
{
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
free(ivars)
}
@end
使用
// 自动归档/解档
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"PersonInfo"];
if (data) {
// 解档使用
Person *person = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"name: %@", person.name);
} else {
Person *person = [Person new];
person.name = @"devZhang";
person.age = @(35);
person.company = @"ShengXue";
person.job = @"iOSDev";
person.address = @"龙岗坂田国际中心";
// 归档存储
data = [NSKeyedArchiver archivedDataWithRootObject:person];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:@"PersonInfo"];
}
未进行归档、解档时报错
// 归档错误
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person encodeWithCoder:]: unrecognized selector sent to instance 0x600000450d40'
// 解档错误
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person initWithCoder:]: unrecognized selector sent to instance 0x60000005d760'
使用自动归档、解档后,自定义类型数据处理正常
2018-07-07 00:26:20.023737+0800 DemoRuntime[1850:81640] name: devZhang