iOS数据持久化之二——归档与设计可存储化的数据模型基类

简介:

iOS数据持久化之二——归档与设计可存储化的数据模型基类

一、引言

        在上一篇博客中,我们介绍了用plist文件进行数据持久化的方法。虽然简单易用,但随着开发的深入,你会发现,这种方式还是有很大的局限性。试想,如果我们可以将用户的登录返回信息模型,游戏中角色的属性信息模型进行直接的持久化存取,那是不是非常爽的事,幸运的是,我们可以通过归档,来设计一个这样的数据模型。

二、先来精通归档吧

        归档也是iOS提供给开发者的一种数据存储的方式,事实上,几乎所有的数据类型都可以通过归档来进行存取。其存储与读取的过程,主要封装在两个类中:NSKeyedArchiver和NSKeyedUnarchiver。

1、归档的原理

        归档是将一种或者多种数据类型进行序列化,解归档的过程就是将序列化的数据进行反序列化的解码,这里需要注意一点,归档的核心并非是数据的持久化处理,而是数据的序列化处理,持久化的处理依然是通过文件存取来实现的。因此,被归档的数据类型都必须遵守一个相同的协议,才能在这个协议的约束下进行正确的归档与解归档,这个协议就是NSCoding协议,我们可以先来看一下NSCoding中的内容:

?
1
2
3
4
5
6
@protocol NSCoding
 
- ( void )encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
 
@end

这个协议非常简单,一个init的归档方法,一个encode的解归档方法,NSCoder就是归档对象。原则上说,无论是什么数据类型的对象,系统的或者是我们自定义的,都可以通过实现这个协议中的方法来支持归档操作。

2、几种归档与解归档的应用

(1)通过类方法来对rootKey进行归档

        这种方式,我个人理解,很类似于NSUserDefaults中的standardUserDefaults,只是后者是系统为我们创建的一个默认plist文件,而rootKey是系统为我们创建的一个默认的归档键值。说起来比较复杂,举个例子就十分清晰了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
     NSString *homeDictionary = NSHomeDirectory(); //获取根目录
     NSString *homePath  = [homeDictionary stringByAppendingPathComponent:@ "atany.archiver" ]; //添加储存的文件名
     //方式一:通过data数据归档,在将数据写入文件
     NSData *data= [NSKeyedArchiver archivedDataWithRootObject:@ "123" ];
     [data writeToFile:homePath atomically:YES];
     //方式二:直接写入文件
     [NSKeyedArchiver archiveRootObject:@ "456"  toFile:homePath];
     //方式一和方式二的效果完全一样 只是解归档的时候不同
     
     //方式一的解归档:先获取data数据,在进行data数据的解归档
     NSLog(@ "%@" ,[NSKeyedUnarchiver unarchiveObjectWithData:data]);
     //方式二的解归档:直接解文件中的归档
     NSLog(@ "%@" ,[NSKeyedUnarchiver unarchiveObjectWithFile:homePath]);

上面的示例是对字符串类型进行的归档,是对单一的数据对象进行的归档,当然,这里的对象是支持数组、字典等集合的,但集合其中的对象,也必须全部支持归档操作。

(2)通过构造新的archiver对象,对多个对象进行归档

        除了上面的类方法,我们还可以自己构造一个归档对象,来对多种不同的对象进行归档:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
     NSString *homeDictionary = NSHomeDirectory(); //获取根目录
     NSString *homePath  = [homeDictionary stringByAppendingPathComponent:@ "atany.archiver" ]; //添加储存的文件名
     //这里创建一个可变的data对象作为归档的容器
     NSMutableData * data = [[NSMutableData alloc]init];
     //创建一个归档对象,归档后写入data数据
     NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
     //对下面的字符串和int值进行归档序列化
     [archiver encodeObject:@ "jaki"  forKey:@ "name" ];
     [archiver encodeInt:24 forKey:@ "age" ];
     //写入data
     [archiver finishEncoding];
     //写入文件
     [data writeToFile:homePath atomically:YES];
     
     //创建解归档的反序列化对象
     NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
     //进行反序列化
     NSString * name = [unarchiver decodeObjectForKey:@ "name" ];
     int  age = [unarchiver decodeIntForKey:@ "age" ];
     //打印信息
     NSLog(@ "\nname:%@\nage:%d" ,name,age);

结果如下:

152058_IsuJ_2340880.png

(3)进行自定义对象的归档

        上面介绍中有提到,原则上,任何遵守了NSCoding协议的类都可以进行归档操作,那么对于我们自定义的对象,我们该如何来做呢?

首先,我们新建一个类:

仿照上面的例子,我们写一个这样的类:

?
1
2
3
4
@interface MyObject : NSObject
@property(nonatomic,strong)NSString * name;
@property(nonatomic,assign) int  age;
@end

对其进行归档:

?
1
2
3
4
5
6
7
8
     //进行归档
     MyObject * obj = [[MyObject alloc]init];
     obj.name = @ "jaki" ;
     obj.age = 24;
     NSData * data =  [NSKeyedArchiver archivedDataWithRootObject:obj];
     //进行解档
     MyObject * obj2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
     NSLog(@ "\nname:%@\nage:%d" ,obj2.name,obj2.age);

直接运行,程序会崩溃掉,打印如下:

152718_u7DM_2340880.png

可以看出,正是我们前边说过的,必须遵守归档协议的对象,才可以被归档,我们在MyObject类中实现如下两个方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//解档方法
- (instancetype)initWithCoder:(NSCoder *)coder
{
     if  (self=[super init]) {
         _name = [coder decodeObjectForKey:@ "name" ];
         _age = [coder decodeIntForKey:@ "age" ];
     }
     return  self;
}
 
//归档方法
- ( void )encodeWithCoder:(NSCoder *)coder
{
     [coder encodeObject:_name forKey:@ "name" ];
     [coder encodeInt:_age forKey:@ "age" ];
}

添加了上面两个方法,我们自定义的对象就可以自由归档存取,并可以写入本地,非常cool吧。

三、设计可以归档存取的数据模型基类

1、动机与初衷

        通过上面对归档的介绍,我们可以发现归档一个十分有潜力的应用:可以自由存取自定义的数据对象。这个特性的优势是毫无疑问的,除了可以使我们的数据用起来更加方便,无需多次解析数据外,安全性也更好。但是也带来了一个缺陷,每个类都需要实现NSCoding中的两个方法是十分繁琐的,并且类越复杂,这个步骤越繁琐,如果在之后的修改和优化中类做了改变,相应的方法也要做改变,这将增加很大的工作量并且埋下潜在bug的风险。

        所以我们会想,能否设计一个这样的model基类,来使需要存储的model都继承于它,使我们的model不需要实现NSCoding方法的同时可以支持归档呢,通过runtime和OC语言特性的一些小技巧,我们是可以做到的。

2、基类模型的设计

        我们新建一个BaseModel类,核心方法如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//归档与解归档的方法
- (instancetype)initWithCoder:(NSCoder *)coder
{
     self = [super init];
     if  (self) {
         //获取所有属性
         NSArray * porpertyArray = [self getAllPropertys];
         for  (NSString * name in porpertyArray) {
             //去掉属性名前面的_
             NSString * key = [name substringFromIndex:1];
             //约定好的键值对 c+key
             [self setValue:[coder decodeObjectForKey:[NSString stringWithFormat:@ "c%@" ,key]] forKey:key];
         }
     }
     return  self;
}
- ( void )encodeWithCoder:(NSCoder *)coder
{
     
     //获取所有属性
     NSArray * porpertyArray = [self getAllPropertys];
     for  (NSString * name in porpertyArray) {
         //去掉属性名前面的_
         NSString * key = [name substringFromIndex:1];
         //约定好的键值对 c+key
         [coder encodeObject:[self valueForKey:key] forKey:[NSString stringWithFormat:@ "c%@" ,key]];
     }
}
//获取model所有属性
-(NSArray *)getAllPropertys{
     NSMutableArray * array = [[NSMutableArray alloc]init];
     
     unsigned  int  * count =  malloc ( sizeof (unsigned  int ));
     //调用runtime的方法
     //Ivar:方法返回的对象内容对象,这里将返回一个Ivar类型的指针
     //class_copyIvarList方法可以捕获到类的所有变量,将变量的数量存在一个unsigned int的指针中
     Ivar * mem = class_copyIvarList([self  class ], count);
     //进行遍历
     for  ( int  i=0; i< *count ; i++) {
         //通过移动指针进行遍历
         Ivar var = * (mem+i);
         //获取变量的名称
         const  char  * name = ivar_getName(var);
         NSString * str = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
         [array addObject:str];
     }
     //释放内存
     free (count);
     //注意处理野指针
     count=nil;
     return  array;
}

通过这样的一个runtime机制,我们可以很方便的是新建的model继承于这个基类,无需其他处理直接支持归档,修改与优化都不受影响。

目录
相关文章
|
3月前
|
存储 iOS开发 开发者
使用克魔助手进行iOS数据抓包和HTTP抓包的方法详解
使用克魔助手进行iOS数据抓包和HTTP抓包的方法详解
47 0
|
3月前
|
Web App开发 网络安全 Android开发
🚀2023最新版克魔助手抓包教程(9) - 克魔助手 IOS 数据抓包
在移动应用程序的开发中,了解应用程序的网络通信是至关重要的。数据抓包是一种很好的方法,可以让我们分析应用程序的网络请求和响应,了解应用程序的网络操作情况。克魔助手是一款非常强大的抓包工具,可以帮助我们在 Android 和 iOS 平台上进行数据抓包。本篇博客将介绍如何使用克魔助手在 iOS 平台上进行数据抓包。
|
存储 iOS开发
iOS小技能: get 和post 布尔值参数处理、按照时间分页的数据重复的处理
1. get 和post 布尔值参数处理:如果后台Bool 参数没有同时支持【 0,1】 ;和【 true false】,get请求的时候就需要特殊处理。 2. 按照时间分页的数据重复的处理
144 0
iOS小技能: get 和post 布尔值参数处理、按照时间分页的数据重复的处理
|
存储 Web App开发 JSON
iOS小技能:设备ID除了使用_idfa、_idfv 还可使用其他替代方案(Keychain 存储)
设备信息的获取:除了使用_idfa、_idfv, 还使用sysct 获取cpu信息。
269 0
iOS小技能:设备ID除了使用_idfa、_idfv 还可使用其他替代方案(Keychain 存储)
|
编解码 iOS开发
ios开发:友盟移动数据统计分析详解
基本指标主要包括:新增用户、启动次数、时段累计日活、分时活跃用户 每个指标都包括具体数值、相对于昨天的增长率。 每个指标都分:今日、昨日、7天前、30天前四个折线图提供分析。
380 0
ios开发:友盟移动数据统计分析详解