iOS数据存储之归档

作者: 若小北00 | 来源:发表于2016-07-08 15:15 被阅读3319次

    在上一篇的文章中我简单介绍了使用偏好设置和属性列表进行数据存储iOS数据存储之偏好设置和属性列表,今天总结下数据存储之归档的简单使用。归档是一种很常用的文件储存方法,几乎任何类型的对象都能够被归档储存(实际上是一种文件保存的形式),与属性列表相反,同样作为轻量级存储的持久化方案,数据归档是进行加密处理的,数据在经过归档处理会转换成二进制数据,所以安全性要远远高于属性列表。我们可以将复杂的对象写入文件中,并且不管添加多少对象,将对象写入磁盘的方式都是一样的。收集了网上的一些资料并结合自己的一些经验,以下将从三个方面简单介绍下归档的使用:

    一、使用archiveRootObject进行简单的归档

    使用NSKeyedArichiver进行归档、NSKeyedUnarchiver进行接档,这种方式会在写入、读出数据之前对数据进行序列化、反序列化操作。

    • 归档:

      //1.获取文件路径
      NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
      //2、添加储存的文件名
      NSString *path  = [docPath stringByAppendingPathComponent:@"data.archiver"];
      //3、将一个对象保存到文件中
      BOOL flag = [NSKeyedArchiver archiveRootObject:@”name” toFile:path]; 
      

    这种方式可以对字符串、数字等进行归档,当然也可以对NSArray与NSDictionary进行归档。返回值Flag标志着是否归档成功,YES为成功,NO为失败。

    • 解档
      //1.获取文件路径
      NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
      NSString *path=[docPath stringByAppendingPathComponent:@"data.archiver"];
      NSLog(@"path=%@",path);
      //2.从文件中读取对象
      [NSKeyedUnarchiver unarchiveObjectWithFile:path]

    使用NSKeyedUnarchiver进行接档(反序列化)。这种归档的方式存在一个缺点:只能把一个对象归档进一个文件中,那么怎么对多个对象进行归档呢?

    二、对多个对象的归档

    同样是使用NSKeyedArchiver进行归档,不同的是同时归档多个对象,这里我们举例放入了一个CGPoint点、字符串、整数(当然很多类型都可以的,例如UIImage、float等等),使用encodeObject:(nullable id)objv forKey:(NSString *)key方法进行归档,最后通过writeToFile方法写入文件。

    • 归档:写入数据
      //准备数据
      CGPoint point = CGPointMake(1.0, 2.0);
      NSString *origin = @"坐标原点";
      NSInteger value = 10;
      NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
      NSString *path = [docPath stringByAppendingPathComponent:@"multi.archiver"];
      NSMutableData *data = [[NSMutableData alloc] init];
      NSKeyedArchiver *archvier = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
      //对多个对象进行归档
      [archvier encodeCGPoint:point forKey:@"kPoint"];
      [archvier encodeObject: origin forKey:@"kOrigin"];
      [archvier encodeInteger:value forKey:@"kValue"];
      [archvier finishEncoding];
      [data writeToFile:path atomically:YES];

    • 解档:从路径中获得数据构造NSKeyedUnarchiver实例,使用encodeObject: forKey:方法获得文件中的对象。
      NSMutableData *dataR = [[NSMutableData alloc] initWithContentsOfFile:multiHomePath];
      NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:dateR];
      CGPoint pointR = [unarchiver decodeCGPointForKey:@"kPoint"];
      NSString *infoR = [unarchiver decodeObjectForKey:@"kInfo"];
      NSInteger valueR = [unarchiver decodeIntegerForKey:@"kValue"];
      [unarchiver finishDecoding];
      NSLog(@"%f,%f,%@,%d",pointR.x,pointR.y,infoR,valueR);

    可以看出对多个对象进行归档还是挺方便的,这里又出现一个问题,这里的对象都是基本类型数据,那么怎么对自己定义类生成的实例对象进行归档呢?

    三、对自定义对象进行归档
    //  YYViewController.m
    #import "YYViewController.h"
    #import "YYPerson.h"
    
    @interface YYViewController ()
    - (IBAction)saveBtnOnclick:(id)sender;
    - (IBAction)readBtnOnclick:(id)sender;
    
    @end
    
    @implementation YYViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    }
    
    - (IBAction)saveBtnOnclick:(id)sender 
    {
        //1.创建对象
        YYPerson *person = [[YYPerson alloc] init];
        person.name=@"蜗牛";
        person.age=23;
        person.height=1.83;
    
        //2.获取文件路径
        NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
        NSString *path=[docPath stringByAppendingPathComponent:@"person.archiver"];
        NSLog(@"path=%@",path);
    
        //3.将自定义的对象保存到文件中,调用NSKeyedArchiver的工厂方法 archiveRootObject: toFile: 方法
        [NSKeyedArchiver archiveRootObject:p toFile:path];
    }
    
    - (IBAction)readBtnOnclick:(id)sender 
    {
        //1.获取文件路径
        NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
        NSString *path=[docPath stringByAppendingPathComponent:@"person.archiver"];
        NSLog(@"path=%@",path);
    
        //2.从文件中读取对象,解档对象就调用NSKeyedUnarchiver的一个工厂方法 unarchiveObjectWithFile: 即可。
        YYPerson * person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        if (person) {
           NSLog(@"%@,%d,%.1f", person.name, person.age, person.height);
        } 
    }
    @end
    

    YYPerson.h

    //  YYPerson.h
    
    #import <Foundation/Foundation.h>
    
    // 如果想将一个自定义对象保存到文件中必须实现NSCoding协议
    @interface YYPerson : NSObject<NSCoding>
    //姓名
    @property(nonatomic,copy)NSString *name;
    //年龄
    @property(nonatomic,assign)int age;
    //身高
    @property(nonatomic,assign)double height;
    @end
    

    YYPerson.m

    //  YYPerson.m
    
    #import "YYPerson.h"
    
    @implementation YYPerson
    
    // 当将一个自定义对象保存到文件的时候就会调用该方法
    // 在该方法中说明如何存储自定义对象的属性
    // 也就说在该方法中说清楚存储自定义对象的哪些属性
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        NSLog(@"调用了encodeWithCoder:方法");
        [aCoder encodeObject:self.name forKey:@"name"];
        [aCoder encodeInteger:self.age forKey:@"age"];
        [aCoder encodeDouble:self.height forKey:@"height"];
    }
    
    // 当从文件中读取一个对象的时候就会调用该方法
    // 在该方法中说明如何读取保存在文件中的对象
    // 也就是说在该方法中说清楚怎么读取文件中的对象
    - (id)initWithCoder:(NSCoder *)aDecoder
    {
        NSLog(@"调用了initWithCoder:方法");
        //注意:在构造方法中需要先初始化父类的方法
        if (self=[super init]) {
            self.name=[aDecoder decodeObjectForKey:@"name"];
            self.age=[aDecoder decodeIntegerForKey:@"age"];
            self.height=[aDecoder decodeDoubleForKey:@"height"];
        }
        return self;
    }
    @end
    
    注意
    • 如果需要归档的类是某个自定义类的子类时,就需要在归档和解档之前先实现父类的归档和解档方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;
    • 必须遵循并实现NSCoding协议
    • 归档和解归档其中任意对象都需要归档和解归档整个文件
    • 归档后的文件是加密的,保存文件的扩展名可以任意指定
    下面分享一个项目中封装的基于归档创建的一个用于本地数据存储的类如下:
    // LocalArchiverManager.h 文件
    
    #import <Foundation/Foundation.h>
    
    @interface LocalArchiverManager : NSObject
    
    /**单例模式,获取请求管理类
     *\param        param:   无
     *\returns      return:     无
     */
    + (LocalArchiverManager *)shareManagement;
    
    /**清除本地的序列化的文件
     *\param        param:   无
     *\returns      return:     无
     */
    - (void)clearArchiverData;
    
    /**保存缓存数据
     *\param        obj:   数据源
     *\param        key:     接口的名称
     *\returns      无
     */
    - (void)saveDataArchiver:(id)obj andAPIKey:(NSString *)key;
    
    /**回去缓存数据
     *\param        obj:   api的key
     *\returns      id:     返回的数据源
     */
    - (id)archiverQueryAPIKey:(NSString *)key;
    @end
    

    LocalArchiverManager.m 文件

    #import "LocalArchiverManager.h"
    
    #define Document [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
    #define ArchiverFile    [Document stringByAppendingPathComponent:@"Archiver"]
    
    @interface LocalArchiverManager()
    
    @property(nonatomic, strong) NSFileManager *fileManager;
    
    @end
    
    @implementation LocalArchiverManager+ (LocalArchiverManager *)shareManagement
    {
        LocalArchiverManager *m_localArchiverMana = nil;
        static dispatch_once_t onceTocken;
        dispatch_once(&onceTocken, ^
        {
            m_localArchiverMana = [[LocalArchiverManager alloc] init];
        });
        return m_localArchiverMana;
    }
    
    - (id)init
    {
        self = [super init];
        if(self)
        {
           self.fileManager = [NSFileManager defaultManager];
        }
        return self;
    }
    
    #pragma mark private methods
    // 检测path路径文件是否存在
    - (BOOL)checkPathIsExist:(NSString *)path
    {
        return [_fileManager fileExistsAtPath:path isDirectory:nil];
    }
    
    // 创建文件
    - (void)createArchiverFile
    {
        if (![self checkPathIsExist:ArchiverFile])
        {
            [self addNewFolder:ArchiverFile];
        }
    }
    
    //新建目录,path为目录路径(包含目录名)
    - (void)addNewFolder:(NSString *)path
    {
        [_fileManager createDirectoryAtPath:path
                 withIntermediateDirectories:YES
                                  attributes:nil
                                       error:nil];
    }
    
    #pragma mark public methods
    
    - (void)clearArchiverData
    {
        NSError *error;
        if([m_fileManager removeItemAtPath:ArchiverFile error:&error])
        {
    
        } else {
            DLOG(@"清除本地序列化的文件失败....:%@",error);
        }
    }
    // 保存数据
    - (void)saveDataArchiver:(id)obj andAPIKey:(NSString *)key
    {
        NSMutableData *data = [[NSMutableData alloc] init];
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
        [archiver encodeObject:obj forKey:key];
        [archiver finishEncoding];
        [self createArchiverFile];
        key = [key stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
        NSString *path = [ArchiverFile stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.text",key]];   
       BOOL isSuc = [data writeToFile:path atomically:YES];
    if(!isSuc) {
            DLOG(@"本地序列化失败key....:%@",key);
        }
    }
    // 获取数据
    - (id)archiverQueryAPIKey:(NSString *)key
    {
        NSString *str = [key stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
        NSString *path = [ArchiverFile stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.text",str]];
        NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile:path];
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        id content = [unarchiver decodeObjectForKey:key];
        [unarchiver finishDecoding];
        DLOG(@"content.....:%@",content);
        return content;
    }
    

    欢迎阅读:

    iOS技术交流群 461069757
    iOS-Ant-Bang互助社区 426981364 欢迎加入!

    相关文章

      网友评论

      • cd3c16604fdf:请问下作者,有完整的demo么? 还有个问题,如果存储用户个人信息的话,该如何存储?用什么方式呢?
      • 颓阿废:请问一下作者,如果一个新闻类啊app,他的新闻列表要缓存的话用哪种缓存方式呢???
        Paco_Ke:这当然用数据库了

      本文标题:iOS数据存储之归档

      本文链接:https://www.haomeiwen.com/subject/aufrjttx.html