iOS开发技巧系列—使用泛型类来实现数据持久化

以前在用C#开发程序的时侯,只要用到数组,必然离不开泛型。再配合集合的扩展方法和LINQ,对于集合数据的操作真是得心应手,非常爽快。后来我转到iOS开发,学会了Objective语言。发现在Objective-C里,一般最常用的数组容器就是NSArray和字典NSDictionary了。可惜这两者都不支持泛型。数据都是以NSObject的类型添加进来,理论上可以保存保存任何的引用类型,这就需要让开发者来确保所有添加的数据类型的一至性。同样的,当从数组容器里取值时,需要将其转化成对应的Model。有时侯还需要进行类型判断。这样的操作不仅增加了代码的复杂性,也很容易出错。而Swift作为一门后起之秀,必然添加了对泛型的支持。弥补了Objective-C对数组操作的安全性,复杂性等的不足,而本篇文章向大家介绍了如果来定义一个泛型类,通过它来作为一种可以永久保存任意数据类型的数据容器,来实现iOS的数据持久化。

什么是泛型

为了更直观一点,首先让我们来看看不支持泛型的Objective-C语言是怎么利用数据容器的

从上面的示例代码可以很清楚地看到,因为Objective-C不支持泛型,所以编译器没有做任何限制,可以在NSMutableArray里添加任何引用类型的数据,这些数据都以id类型保存。在取值时也一样,默认取出来的数据都是id类型,需要将其转化成原先保存的类型才行。这些操作都是需要开发者来确保正确性。如果不小心写错了,编译器也不会给出任何提示或者警告,只有在运行程序时才能触发错误。所以很容易出现隐藏的Bug,同时类型转换也增加了代码的复杂性。

而泛型的出现刚好完全解决了上面的问题:

从上面的代码很容易看出,对于数据类型是Int的数组,只能添加Int类型的数据,其他的数据类型编译器都会报错。取值也一样,取出来的数直接就是Int类型了,不需要再转型了。
而且比较方便的是Swift的数据容器是是可以直接保存值类型的,不需要像Objective-C一样需要将其转成NSNumber了。同样,你也可以声明一个类型为String类型的数组([String]),
或者自定义类的的泛型数组,编译器都会帮你在编码时告诉你正确的数据类型,防止开发者添加错误的数据类型。

使用泛型的优点

使用泛型的优点有很多:

  • 泛型提供了一个强类型的编程模型
  • 编译时的类型检查减少了运行时发生数据类型转换异常的几率
  • 简化了代码,缓解了代码膨胀。
  • 性能得到了提升,不需要在运行时再做类型检查。
  • 代码的可读性更好,并且有更好的代码智能提示。

其实在最新的XCode 7.X中,苹果也悄悄地加入了Objective-C语言的弱泛型支持,见下面代码。

可以在声明NSMutableArray时添加一个弱泛型约束,之所以是弱泛型,是因为编译器会帮你检查数据类型是否正确,如果不正确会有一个警告,但是不会强制报错,代码还是可以编译过的。

写代码时XCode会自动提示它你应该添加什么类型的数据

取值也会告诉你里面保存了什么类型的数据

 

如果你把错误的数据类型存进去,编译器会警告,但不是强制报错。

iOS数据持久化方案的问题

数据存储在APP开发中起着至关重要的作用。相信各位开发者在开发过程中都有碰到如下情况:

  • 需要一些全局变量,来记录APP的一些设置或者是频繁变动的数据
  • 页面之间或者各种View之间的传值,
  • 需要临时缓存一些数据。

这些数据存储的处理在开发过程极为常见,而且有一个共同点就是处理的各种数据类型完全不一样,有时是各种数字(NSNumber),也有很常用的字符串,当然各种数组或者字典也是不可少的。
所以由此带来的一个问题就是这些数据需要以什么的格式保存,保存后取出来又要如何转化成原先的数据类型,这些都是要手写代码去处理。前面已经说过,泛型正好是解决此类问题而生。但在这里并不是使用泛型数据容器,而是使用泛型类。那么怎么使用泛型类来写一个通用的数据存储框架,可以解决以上问题呢?

首先iOS的数据存储离不开iOS存储本身的机制,也就是那如下几种:

  • Plist
  • 归档&NSUserDefault
  • SQLite3
  • CoreData

关于这些数据持久化的介绍和使用方式网络上有很多的文章讲解,在这里我就不详述了。而且目前市面上还有不少对这些数据持久化API的封装开源库,比如说著名的FMDB,但是都没有完全解决上面的问题。我们需要一个轻量级,可以在代码文件的任何位置读写,支持缓存,临时存储(APP退出后数据丢失)和数据发生变化时的监视,同时不需要做数据类型的转换且可以保存任何类型数据的数据存储方案。而Swift泛型的出现,使得这种存储方案成为可能。

使用泛型类来存储数据

和泛型数据容器相比,自定义泛型类用得并不多。可能许多初级开发者不好理解泛型类,更不明白泛型类有什么作用。关于这个,可以去参考Swift的Array或者是NSDictionry的定义,并且练下手会更好一点。下面我们来定义一个泛型类

上面就是这个泛型存储类的全部私有字段。各有什么作用注释里都有说明,下面上它的两个构造器

上面是泛型类的两个构造器。因为其参数是T类型,也就是泛型,那么就可以往里在传任何类型。同时这是一个默认值,如果你没有设定值的话,那也是可以取出值的,就是这个默认值。
后面的构造器就是带缓存有的了。如果你传的缓存时间大于0,那么这个时间(秒为单位)就是缓存的时间,如果小于等于0,那就这是一个临时存储。它不会写到硬盘里面,只保存在内存里。
下面上这个泛型类最核心的属性,

这里我用了伪代码,同时省略了一些功能。因为原代码比较长也有点复杂,这里有几个关键点要说明下

  1. 最上面的isExpire是个计算属性,用来判断保存的数据有没有过期,如果是永久存在的,就直接返回false,如果不是,那么根据timeoutDate来判断有没有过期

    如果已经过期,那么需要清空数据,再将hasValue设成false。
  2. 如果hasValue是false,也就是说内存中不存在该值,那么就需要到存储仓库去看了。这里面我省略了一些代码。总体思路如下:
    先判断存储仓库有没有,如果没有,就直接设置成默认值,再保存到存储仓库里。
    如果存在,就从存储仓库里取出,最后再将hasValue设置成true。
    在这里存储仓库是指另一个对iOS数据持久化封装的库,你可以用Github常用的开源库,也可以自己写。
  3. 其实对于取出来的数据上需要转换的,由Object转换成T类型就行了。
  4. 对于设值,首先判断是不是临时保存的,如果不是,那么需要将其保存到存储仓库,最后再将hasValue设成true就行了。

上面就是这个泛型类最核心的功能的代码实现。这里面其实是对最常见的valueForKey和setValueForKey二次封装,并且再将数据再转换成T类型。因为在用构造器实例化这个类时,
它就会根据传入的默认值得知保存的数据是什么类型的。所以便可以正确地转换成原先的类型。

至于这些细节实现代码,比如数据值变化时的监视,每次设定值时更新缓存过期的时间,怎么将数据保存到存储仓库里,以及清空功能等,读者有兴趣可以去看源码GrandStore。 我在里面是采用归档的形式来将保存各种数据的。并且在我做的所有项目里都用了这个库。不过需要注意的是,因为Objective-C没有泛型支持,所以目前GrandStore可能不支持Objective-C。只能用到Swift环境中。如果觉得不错,请给个Star哦!

使用GrandStore

使用GrandStore十分简单,可以直接写在全局变量里面。所以项目里的任何文件都可以直接访问。
比如你可以用一个值来记录APP是不是第一次开启

怎么样,用这个来存储一些全局的变量可以是说十分的方便,无论是读取还是写入,都不需要特定的方法,也不需要进行数据类型转换,直接使用就行。
同样的,还可以用它来保存其他的任何类型,比如:

因为我是用归档来实现上面所说的存储仓库的。所以如果我想用GrandStore来保存自定义对象,那么要让它实现NSCoding协议,我在前面写了一系列的文章打造强大的BaseModel中有实现自动归档的GrandModel,所以可以在这里直接使用。
无论是数组还是字典都毫无压力

更多的使用示例请参考我的Github,上面有链接

总结

Swift泛型的出现弥补了Objective-C没有泛型的去缺陷,更是带来了更多现代编程语言诸多的便捷特性。使用Swift的泛型,我们有了一个最好的强类型编程模式。在此基础上,泛型方法,闭包,高阶函数
等有了更好的用武之地,极大的提升了开发效率。我在此建议会Objective-C的开发者尝试去学习Swift,掌握它,你会发现另一片天地。

1 1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部