iOS本地数据存储方案汇总

一。

常见存储方式

  • Plist 格式文件存储
  • NSUserDefaults 沙盒存储(个人偏好存储)
  • 文件读写储存
  • 解归档存储
  • 数据库存储
  • Keychain 存储(可解决设备唯一标识问题)

Demo 示例

  • 文件读写操作、沙盒操作、解归档操作、依赖 FMDB 的数据库操作 以及 UIWeb/WKWeb 缓存清理 、NSPredicate 数据筛查等操作 DataStore Git地址
  • Demo 做笔记使用,持续更新 。。。

数据存储基础

  • 作为移动端开发工程师,所需要的数据几乎全部都是通过网络获取,而且网络请求都有时耗;在网络好的情况下这种时耗虽然不足考虑,但是一旦网络环境不好,会很影响产品体验。网络环境无法控制,但是对于一些数据不经常变动的网络请求或没必要实时更新的数据,我们可以选择将网络数据缓存本地,适时更新。

  • 在iOS中涉及存储方式不外乎这几种,只是大家各自的分类方式可能有些不同。这里仅是自己的思考与分类。

  • 了解缓存,有必要先了解一下沙盒这个概念。
    沙盒其实质就是在iOS系统下,每个应用在内存中所对应的储存空间。
    每个iOS应用都有自己的应用沙盒(文件系统目录),与其他文件系统隔离,各个沙盒之间相互独立,而且不能相互访问(手机没有越狱的情况下)。
    各个应用程序的沙盒是相互独立的,在系统内存消耗过高时,系统会收到内存警告并自动将一些退出软件。这就保证了系统的数据的安全性及系统的稳定性。

  • 一个应用的沙盒目录如下:

 
  • Documents 应用程序在运行时生成的一些需要长久保存的数据。
  • Library/Caches 储存应用程序网络请求的数据信息(音视频与图片等的缓存)。此目录下的数据不会自动删除,需要程序员手动清除该目录下的数据。主要用于保存应用在运行时生成的需要长期使用的数据.一般用于存储体积较大数据。
  • Library/Preferences 设置应用的一些功能会在该目录中查找相应设置的信息,该目录由系统自动管理,通常用来储存一些基本的应用配置信息,比如账号密码,自动登录等。
  • tmp 保存应用运行时产生的一些临时数据;应用程序退出、系统空间不够、手机重启等情况下都会自动清除该目录的数据,iTunes或iCloud也不会对其进行备份。无需程序员手动清除该目录中的数据。
  • SystemData - 近期优化项目数据存储,发现多了该文件路径,暂未做过多研究。

Plist 格式文件存储

  • plist文件,即属性列表文件。
  • 可以存储的数据类型有 Array Dictionary String Boolean Date Data Number。
  • 常用于储存用户的设置 或 存储项目中经常用到又不经常修改的数据。
  • 创建 .plist 文件可以使用可视化工具即Xcode ,也可以使用代码。
  • 不适合存储大量数据,而且只能存储基本数据类型。
  • 虽然可以实现 :增 删 改 查 等操作,但由于数据的存取必须是一次性全部操作,所以性能方面表现并不好。

NSUserDefaults 沙盒存储(个人偏好存储)

  • 补充几个方法:

isSubclassOfClass :参数为类 - 参数类为其子类或本身 ;

isMemberOfClass :参数为实例对象 - 参数所属类为其本身 ;

isKindOfClass :参数为实例对象 - 参数所属类为其子类或本身 。

  • 应用程序启动后,会在沙盒路径Library -> Preferences 下默认生成以工程bundle为名字的 .plist 文件 , 该方式存储的数据即存进该文件当中。
  • 常用语存储用户的个人偏好设置。
  • 这种方式本质是操作plist文件,所以性能方面的考虑同plist文件数据储存。
 

文件读写储存

  • 文件操作可通过单例 NSFileManager 处理。文件存储的路径可以代码设置。
  • 可以存储大量数据,对数据格式没有限制。
  • 但由于数据的存取必须是一次性全部操作,所以在频繁操作数据方面性能欠缺。

解归档存储

  • plist 与 NSUserDefaults(个人偏好设置)两种类型的储存只适用于系统自带的一些常用类型,而且前者必须拿到文件路径,后者也只能储存应用的主要信息。
  • 对于开发中自定义的数据模型的储存,我们可以考虑使用归档储存方案。
  • 归档保存数据,文件格式自己可以任意,没有要求 ; 即便设置为常用的数据格式(如:.c .txt .plist 等)要么不能打开,要么打开之后乱码显示。
  • 值得注意的是使用归档保存的自定义模型需要实现NSCoding协议下的两个方法。
  • 不适合存储大量数据,可以存储自定义的数据模型。
  • 虽然归档可以存储自定义的数据结构,但在大批量处理数据时,性能上仍有所欠缺。
数据库存储
  • SQLite : 它是一款轻型的嵌入式数据库,安卓和ios开发使用的都是SQLite数据库;占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了;而且它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快。

  • FMDB 正式基于 SQLite 开发的一套开源库。使用时,需要自己写一些简单的SQLite语句。

  • CoreData 是苹果给出的一套基于 SQLite 的数据存储方案;而且不需要自己写任何SQLite语句。该功能依赖于 CoreData.framework 框架,该框架已经很好地将数据库表和字段封装成了对象和属性,表之间的一对多、多对多关系则封装成了对象之间的包含关系。

  • Core Data的强大之处就在于这种关系可以在一个对象更新时,其关联的对象也会随着更新,相当于你更新一张表的时候,其关联的其他表也会随着更新。Core Data的另外一个特点就是提供了更简单的性能管理机制,仅提供几个类就可以管理整个数据库。由于直接使用苹果提供的CoreData容易出错,这里提供一个很好的三方库 MagicalRecord 。

  • 关于 Core Data 想吐槽的是,数据存储功能虽然封装的很好、功能很强大,但是版本迭代中反复修改数据模型、新增数据模型等问题引起的数据库迁移问题给开发工作带来很多不必要的工作;尤其有未解除过该技术的新人加入。

  • Pod 添加 MagicalRecord 依赖库之后,文件创建 - 数据实体创建 - 数据迁移 :

新建工程 ,Use Core Data 可选可不选,这就给未勾选该项的旧工程使用 Core Data 技术提供可能

创建 .xcdatamodeld 文件

创建实体、新增属性

创建实体相应的关联文件

数据库迁移步骤 1

数据库迁移步骤 2

缓存系统

APP缓存系统设计简要记录(整理中)
HybridAPP(整理优化中)

  • 对大多数 APP 而言,都是 Hybrid 开发,Web 页与原生同时存在,其中 Web 页可能是 UIWeb 也可能是 WKWeb 。所以与之相应的缓存系统,应该包括 Web 缓存与 原生接口数据缓存两部分。

  • Web 缓存有网络缓存及 Webkit 框架机制内的缓存。这里也可以依据 URL + 时间戳 自行设计一套缓存。

  • 原生接口部分的数据缓存

    1. 与用户相关的信息、单个标记符标识等常采用沙盒存储。
    2. 全局使用到的数据模型,需要永久存储的话可以考虑归档;例如用户登陆后的个人信息数据模型。
    3. 倘若不涉及大规模数据的增删改查等操作,可以考虑文件读写的方式直接存储网络返回的 JSON 对象,借助 YYCache 亦可实现高性能存储。
    4. 大规模数据的存储例如帖子、评论、新闻、外卖、商品等可以考虑使用数据库:FMDB (DataStoreDemo - JXFMDBMOperator)或 Core Data (推荐使用 CoreData 的封装库 MagicalRecord);也可以简单点直接使用 NSPredicate 对数据的筛查操作,简单直接。

https://www.jianshu.com/p/ca9d8346e7d6

 

二。

iOS本地数据持久化的几种类型
iOS本地数据持久化几种类型的应用场景及使用

一,iOS本地数据持久化的类型:
1,NSUserDefaults
2,plist
3,Keychain(钥匙串)
4,归档
5,沙盒写入
6,数据库

二,应用场景
1,NSUserDefaults
用于存储用户的偏好设置和用户信息,如用户名,是否自动登录,字体大小等.
数据自动保存在沙盒的Libarary/Preferences目录下.
NSUserDefaults将输入的数据储存在.plist格式的文件下,这种存储方式就决定了它的安全性几乎为0,所以不建议存储一些敏感信息如:用户密码,token,加密私钥等!
它能存储的数据类型为:NSNumber(NSInteger、float、double),NSString,NSDate,NSArray,NSDictionary,BOOL.
不支持自定义对象的存储.

NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    [userDefault setInteger:1 forKey:@"integer"];
    [userDefault setBool:YES forKey:@"BOOl"];
    [userDefault setFloat:6.5 forKey:@"float"];
    [userDefault setObject:@"123" forKey:@"numberString"];
    NSString *numberString = [userDefault objectForKey:@"numberString"];
    BOOL myBool = [userDefault boolForKey:@"BOOl"];
    [userDefault removeObjectForKey:@"float"];
    [userDefault synchronize];

需要注意的问题:
NSUserDefaults存储的数据都是不可变的,想将可变数据存入需要先转为不可变才可以存储.
NSUserDefaults是定时把缓存中的数据写入磁盘的,而不是即时写入,为了防止在写完NSUserDefaults后程序退出导致的数据丢失,可以在写入数据后使用synchronize强制立即将数据写入磁盘.

2,plist
即属性列表文件,全名是Property List,这种文件的扩展名为.plist,因此,通常被叫做plist文件。它是一种用来存储串行化后的对象的文件,用于存储程序中经常用到且数据量小而不经常改动的数据。
可以存储的类型:NSNumber,NSString,NSDate,NSData ,NSArray,NSDictionary,BOOL.
不支持自定义对象的存储.

plist的创建方式有两种:command + n 创建和纯代码创建,不同的创建方式使用方法也自然不同

command + n 创建:
创建:(创建方法自行百度)
读取:

NSString *plistPath = [[NSBundle mainBundle]pathForResource:@"myTest" ofType:@"plist"];
    NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
    /**如果为数组时*/
//    NSMutableArray *dataArray = [NSMutableArray arrayWithContentsOfFile:plistPath];
    NSLog(@"%@",dataDic);

修改:

[dataDic setObject:@"男" forKey:@"sex"];
[dataDic setObject:@15 forKey:@"age"];
[dataDic writeToFile:plistPath atomically:YES];
NSLog(@"----%@",dataDic);

纯代码创建:
创建:

NSArray *sandBoxPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [sandBoxPath objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"myTestPlist.plist"];
    NSLog(@"%@",plistPath);

写入:

NSMutableDictionary *dic = [NSMutableDictionary dictionary];
[dic setObject:@"18" forKey:@"age"];
[dic setObject:@"胡杨" forKey:@"name"];
[dic writeToFile:plistPath atomically:YES];

读取:

NSArray *sandBoxPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [sandBoxPath objectAtIndex:0];
    NSString *plistPath = [documentsPath stringByAppendingPathComponent:@"myTestPlist.plist"];
    NSLog(@"%@",plistPath);
    
    NSMutableDictionary *dataDic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
    NSLog(@"-----%@",dataDic);

修改:
与command+n方法相同.

需要注意的问题:
如果需要存储自定义类型的数据需要先进行序列化!

3,Keychain
用于本地重要数据的存储,将数据加密后存储在本地更安全.如:密码,秘钥,序列号等.当你删除APP后Keychain存储的数据不会删除,所以在重装App后,Keychain里的数据还能使用。从ios 3.0开始,跨程序分享keychain变得可行而NSUserDefaults存储的数据会随着APP而删掉.
使用keychain时苹果官方已经为我们封装好了文件KeychainItemWrapper,引入即可使用.当然也可是使用其他优秀的第三方的封装,比如ssKeychain,使用方法如下:
使用方法

4,归档(NSKeyedArchiver)
归档是iOS开发中数据存储常用的技巧,归档可以直接将对象储存成文件,把文件读取成对象。
相对于plist或者userdefault形式,归档可以存储的数据类型更加多样,并且可以存取自定义对象。对象归档的文件是保密的,在磁盘上无法查看文件中的内容,更加安全。
遵守NSCoding协议,并实现该协议中的两个方法。如果是继承,则子类一定要重写那两个方法。因为子类在存取的时候,会去子类中去找调用的方法,没找到那么它就去父类中找,所以最后保存和读取的时候新增加的属性会被忽略。需要先调用父类的方法,先初始化父类的,再初始化子类的。
保存数据的文件的后缀名可以随意命名。
demo

5,沙盒写入
持久化在Document目录下,一般存储非机密数据。当App中涉及到电子书阅读、听音乐、看视频、刷图片列表等时,推荐使用沙盒存储。因为这可以极大的节约用户流量,而且也增强了app的体验效果.

Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
Documents: 保存应?运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录。
tmp: 保存应?运行时所需的临时数据,使?完毕后再将相应的文件从该目录删除。应用 没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。
Library/Caches: 保存应用运行时?成的需要持久化的数据,iTunes同步设备时不会备份 该目录。?一般存储体积大、不需要备份的非重要数据,比如网络数据缓存存储到Caches下
Library/Preference: 保存应用的所有偏好设置,如iOS的Settings(设置) 应?会在该目录中查找应?的设置信息。iTunes同步设备时会备份该目录。

// 获取程序的Home目录       
NSString  *path = NSHomeDirectory();     

// 获取Document目录       
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;  
     
// 获取Cache目录       
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;   
     
// 获取Library目录       
NSString *path = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;

// 获取Tmp目录       
NSString *path = NSTemporaryDirectory();

写入:

NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"test.txt"];
NSLog(@"%@",filePath);
NSArray *array = @[@"1",@"2",@"3"];
    BOOL isSuccess = [array writeToFile:filePath atomically:YES];
    if (isSuccess) {
        NSLog(@"成功");
    } else {
        NSLog(@"失败");
    }

读取:

NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [path stringByAppendingPathComponent:@"test.txt"];
    NSLog(@"%@",filePath);
NSArray *tmpArray = [NSArray arrayWithContentsOfFile:filePath];
    NSLog(@"%@",tmpArray);

6,数据库
适合储存数据量较大的数据,一般使用FMDB和CoreData来实现.

FMDB:
FMDB是iOS平台的SQLite数据库框架,FMDB以OC的方式封装了SQLite的C语言API,使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码,对比苹果自带的Core Data框架,更加轻量级和灵活,提供了多线程安全的数据库操作方法,有效地防止数据混乱。

CoreData:
Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,我们不需要编写任何SQL语句.但是直接操作CoreData显的不是那么容易,所以我多数的时候会使用MagicRecord来实现.MagicRecord是对CoreData的二次封装,使用起来简单操作方便.

FMDB 和 MagicRecord 的 demo

FMDB和MagicRecord的性能方面各有千秋,需要根据项目的实际需求进行选择.没有最好的方案只有最适合的方案!

FMDB与MagicRecord的性能比较

数据迁移:
如果使用到数据库那就不得不提数据迁移的问题,不管是MagicRecord还是FMDB如果要更新数据库都要进行数据迁移.

FMDB的数据迁移:
判断表中有没有这个字段,如果没有使用sq语句插入.

/**添加字段/数据迁移*/
- (void)dataMigrationWithTableName:(NSString *)tableName newAdded:(NSString *)newAdded block:(FMDBblock)block
{
//tableName:表名
//newAdded:新加字段名
    [_queue inDatabase:^(FMDatabase * _Nonnull db) {
        if (![db columnExists:newAdded inTableWithName:tableName]){
            NSString *sql = [NSString stringWithFormat:@"ALTER TABLE %@ ADD %@ INTEGER",tableName,newAdded];
            BOOL success = [db executeUpdate:sql];
            if (success) {
                block(YES,@"字段添加成功");
            } else {
                block(NO,@"字段添加失败");
            }
        }
    }];
}

MagicRecord的数据迁移:
首先在初始化的时候要注意初始化语句.

/**以程序名为数据库名,不需要自动升级*/
    [MagicalRecord setupCoreDataStack];
    /**以程序名为数据库名,需要自动升级*/
    [MagicalRecord setupAutoMigratingCoreDataStack];
    /**自定义数据库名,不需要自动升级*/
    [MagicalRecord setupCoreDataStackWithStoreNamed:@"SQDemo.sqlite"];
    /**自定义数据库名,需要自动升级*/
    [MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:@"SQDemo.sqlite"];

next 新建一个模型的新版本


 
iOS本地数据存储方案汇总
image.png

next 添加新字段并且选择model2为当前模型


 
iOS本地数据存储方案汇总
image.png

最后为model2新建实体类就可以了!

如果有写的不好的地方欢迎各位指出!
参考:
https://blog.csdn.net/a416863220/article/details/48787723
https://www.jianshu.com/p/cd1a06c7ce09
http://suree.org/2015/09/29/DatabaseThink/



iOS本地数据存储方案汇总

上一篇:ApplicationLoader 上传中断带来的问题


下一篇:最近开始学习Android一