数据储存

归档-解归档

2021-03-29  本文已影响0人  KG丿夏沫

归档-解归档

在iOS开发中,数据持久化方法很多,比如:SQL、KeyChina、CoreData、NSUserDefaults、归档、文件等等方式。今天一起探索归档以及解归档,通俗点说:归档就是对数据加密后进行文件存储,解归档就是读取文件然后对数据解密,转换成我们需要的数据类型。

归档-解归档简单使用

归档就是将想要存储的数据通过加密后保存为文件的一种数据持久化方式,在iOS12以前我们使用[NSKeyedArchiver archiveRootObject:person toFile:@""];这个方法直接实现归档的,返回一个BOOL值,确定是否归档成功,如果需要归档的对象是一个类对象,那么类对象需要遵守NSCoding协议。但是从iOS12之后,这个方法就被废弃了,虽然可以用,但是一直提示你,方法被弃用,推荐使用NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:&error];方法,并且不再是遵守NSCoding协议了,而是NSSecureCoding协议,这个协议是NSCoding协议的子类,并且在NSCoding协议的基础上做了一些扩展。

+ (nullable NSData *)archivedDataWithRootObject:(id)object requiringSecureCoding:(BOOL)requiresSecureCoding error:(NSError **)error这个方法有三个参数,第一个参数就一个id类型的对象,是你需要归档操作的对象。第二个参数是是否需严格安全加密,推荐使用YES,因为使用NO的时候,很可能因为加密过程中不严格,在解归档的时候数据出现乱码。第三个参数是错误信息,如果归档失败就会返回错误信息,通过这个error获取。

下面先一起看下传统的归档操作,也就是比较笨拙的归档操作。

1、我们先创建一个基于NSObject的万能KGPerson类,并且遵守NSSecureCoding协议,然后在.h文件中申明几个属性。代码如下:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface KGPerson : NSObject<NSSecureCoding>

@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,assign) float height;
 
@end

NS_ASSUME_NONNULL_END

2、然后我们在.m文件中实现协议方法,代码如下:

#import "KGPerson.h"

@implementation KGPerson

- (instancetype)initWithCoder:(NSCoder *)coder{
    if (self = [super init]) {
        _age = [coder decodeIntegerForKey:@"age"];
        _name = [coder decodeObjectForKey:@"name"];
        _height = [coder decodeFloatForKey:@"height"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder{
    [coder encodeInteger:_age forKey:@"age"];
    [coder encodeObject:_name forKey:@"name"];
    [coder encodeFloat:_height forKey:@"height"];
}

+ (BOOL)supportsSecureCoding{
    return YES;
}

@end

到这里我们在KGPerson类的处理就完成了。下面我们看下怎么使用,首先我们在ViewController里面导入头文件,然后添加两个方法,一个归档一个解归档。代码如下:

#import "ViewController.h"
#import "KGPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}
- (IBAction)save:(id)sender {
    
    KGPerson *person = [[KGPerson alloc] init];
    
    person.name = @"KG丿夏沫";
    person.age = 28;
    person.height = 180;
    
    NSError *error = nil;
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:&error];
    
    if (error) {
        NSLog(@"文件归档失败,错误日志如下:%@",error);
    }else{
        NSString *tempPath = NSTemporaryDirectory();
        
        NSString *filePath = [tempPath stringByAppendingPathComponent:@"KG丿夏沫.data"];
        
        NSFileManager *manager = [NSFileManager defaultManager];
        
        BOOL isHave = [manager fileExistsAtPath:filePath];
        
        if (isHave) {
            NSError *removeError = nil;
            BOOL removeStatus = [manager removeItemAtPath:filePath error:&error];
            if (removeError) {
                NSLog(@"文件存在,但是删除失败,错误日志:%@",removeError);
            }
            
            if (removeStatus) {
                NSLog(@"文件存在,删除成功");
            }
        }
        
        BOOL isSuccess = [manager createFileAtPath:filePath contents:data attributes:nil];
        
        if (isSuccess) {
            NSLog(@"归档成功,文件路径如下:%@",filePath);
        }else{
            NSLog(@"文件归档写入失败");
        }
    }
    
}

- (IBAction)read:(id)sender {
    
    NSString *tempPath = NSTemporaryDirectory();
    
    NSString *filePath = [tempPath stringByAppendingPathComponent:@"KG丿夏沫.data"];
    
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    
    NSError *error = nil;
    
    KGPerson *person = [NSKeyedUnarchiver unarchivedObjectOfClass:NSClassFromString(@"KGPerson") fromData:data error:&error];
    
    if (error) {
        NSLog(@"接归档失败,错误如下:%@",error);
    }else{
        NSLog(@"%@的信息如下:\n身高为:%.f\n年龄为:%ld\n",person.name,person.height,(long)person.age);
    }
}
@end

到这里基本上归档-解归档的基本使用就介绍完成了,但是我们开发中不会说一个类的属性就这么几个,那么当一个类有很多属性的时候,我们不可能还是按照刚才的做法一步一步操作,那样不说代码量大,而且很容易出错,所以我们接下来给KGPerson这个类进行优化。

归档-解归档利用runtime优化

当类的属性很多的时候,我们挨个敲一遍,而且还需要对应的类型进行归档,这样特别费劲而且费时,所以我们可以利用runtime来处理这些。首先runtime有一个api<class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)>可以让我们获取到类对应的所有属性,当我们获取到了类的所有属性后我们可以通过KVC获取到属性对应的值。思路整理好了后,我们一起看下代码实现:

1、首先利用runtime获取类对应的所有属性,具体代码如下:

unsigned int count = 0;
Ivar *list = class_copyIvarList(objc_getClass("KGPerson"), &count);

2、获取到类所有的属性后,我们用KVC获取类的属性对应的值,代码如下:

Ivar ivar = list[i];
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];

3、而且我们这要获取或者存储的时候,不需要考虑是字符串还是整型或者是浮点型,系统会自动判断。完整代码如下:

#import "KGPerson.h"
#import <objc/runtime.h>

@implementation KGPerson

- (instancetype)initWithCoder:(NSCoder *)coder{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *list = class_copyIvarList(objc_getClass("KGPerson"), &count);
        if (count > 0) {
            for (int i = 0; i < count; i++) {
                Ivar ivar = list[i];
                const char *name = ivar_getName(ivar);
                NSString *key = [NSString stringWithUTF8String:name];
                id value = [coder decodeObjectForKey:key];
                [self setValue:value forKey:key];
            }
        }
        free(list);
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder{
    unsigned int count = 0;
    Ivar *list = class_copyIvarList(objc_getClass("KGPerson"), &count);
    if (count > 0) {
        for (int i = 0; i < count; i++) {
            Ivar ivar = list[i];
            const char *name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [self valueForKey:key];
            [coder encodeObject:value forKey:key];
        }
    }
    free(list);
}

+ (BOOL)supportsSecureCoding{
    return YES;
}

@end

总结

主要利用runtime的api,来获取类的属性,以及通过iavr获取属性名。然后进行取值或者赋值操作。

上一篇下一篇

猜你喜欢

热点阅读