• iOS: 数据持久化方案


    数据持久化方案(如果总结不到位,或者有误的地方,敬请斧正)

     

    一、功能:

        主要是将数据持久化到本地,减少对网络请求的次数,既节省了用户的流量,也增强了App的体验效果。

     

    二、种类:

         plist存储:使用XML键值对持久化,它适用于少量且数据基本不怎么改变的情况。

            偏好存储:使用NSUserDefalut持久化,专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。

            归档序列化存储:使用二进制序列化持久化,只要遵循了NSCoding协议的对象都可以通过它实现序列化。

            沙盒存储:持久化在Document目录下,一般持久化一些文件,比如图片,音频,视频等,文件沙盒存储主要存储非机密数据。

            本地数据库存储:适合储存大规模数据,管理方便,不过操作稍微复杂一些。

     

    三、详解:

    1、plist存储

    定义:

      plist文件,即属性列表文件,全名是Property List,这种文件的扩展名为.plist,因此,通常被叫做plist文件。

    作用:

      它是一种用来存储串行化后的对象的文件,在iOS开发中通常用来存储用户设置,还可以用于存储程序中经常用到而不经常改动的数据。

    问题:

    (1)什么数据适合存储?

        能存储NSString、NSArray、NSDictionary、NSData、NSDate、NSNumber、Boolean不能存储自定义对象   

    (2)存到什么地方?

        写入创建的.plist文件中

    (3)使用场景?

        plist常用于存储长时间不容易发生变化的数据,例如省市列表、车辆名称列表之类的数据等,这些数据可以保存在 plist 文件里,所以plist适用于存储小型数据,不推荐用plist做缓冲。        

    (4)如何使用?

       存储 

    [dict writeToFile:filePath atomically:YES]; // 字典
    [array writeToFile:filePath atomically:YES]; // 数组
    [string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; // 字符串

        获取

    NSArray *arr = [NSArray arrayWithContentsOfFile:filePath];  // 数组
    NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath]; // 字典  
    NSString *string = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 字符串       

    (5)有什么缺点?

              因为所有的数据都放在root dictionary里,每次读取都要把整个root dictionary取出来再取需要的对象.如果plist文件缓存了几十M的数据.这样很费内存和时间。

     

    2、偏好存储

    定义:

      User Defaults 顾名思义就是一个用户为系统以及程序设置的默认值。

      每个用户都有自己的一套数据,用户和用户之间没法共享的。在苹果的API中,提供了一个类去存储用户的偏好设置。

      这个方法推荐只存储用户的偏好设置,不要存储一些字典、数组之类的。

    作用:

      很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置

       iOS提供了一套标准的解决方案来为应用加入偏好设置功能,就是每一个app都有一个plist文件专门用以保存偏好设置数据。

      每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。

    问题:

    (1)什么数据适合存储?

        可以存储OC定义的所有数据类型,包括对象(系统和自定义的)类型、基本数据类型,如NSInteger等。

    (2)存到什么地方?

        NSUserDefault 本地保存的位置是Library/Preferences 这个目录下的 plist 文件。  

    (3)使用场景?           

             在App中,有时候我们需要将一些信息进行短期的保存,方便用户下次更方便使用App,减少多余的操作,增强用户体验。

             比如,保存用户名、字体大小、是否自动登录等。

    (4)如何使用? 

            存储

    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; [defaults setObject: forKey:]; 
    [defaults setInteger:forKey:]; 
    [defaults setDouble: forKey:]; 
    [defaults setObject: forKey:];
    [defaults synchronize];

        获取 

    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
    [defaults objectForKey:];
    [defaults objectForKey:];
    [defaults integerForKey:];
    [defaults doubleForKey:];

    (5)注意事项?

    • 偏好设置是专门用来保存应用程序的配置信息的, 一般情况不要在偏好设置中保存其他数据。
    • 如果利用系统的偏好设置来存储数据, 默认就是存储在Preferences文件夹下面的,偏好设置会将所有的数据都保存到同一个文件中。
    • 使用偏好设置对数据进行保存之后, 它保存到系统的时间是不确定的,会在将来某一时间点自动将数据保存到Preferences文件夹下面,如果需要即刻将数据存储,可以使用[defaults synchronize];
    • 所有的信息都写在一个文件中,对比简单的plist可以保存和读取基本的数据类型。

     

    3、归档序列化存储

    定义:

             对象归档是iOS中数据持久化的一种方式。 归档是指另一种形式的二进制序列化,但它是任何对象都可以实现的更常规的类型。

    作用:

             使用对模型对象进行归档的技术可以轻松将复杂的对象写入文件,然后再从中读取它们。

    问题:

    (1)什么数据适合存储?

        要使用对象归档,则归档的对象所属类中实现的每个属性都是标量,或者都是遵循NSCoding协议和NSCopying协议的某个类的实例对象。

    (2)存到什么地方?

        对象归档后将得到一个后缀为.archive的文件,数据就保存在了这个文件中。

    (3)使用场景?

        定义某个实例,如果需要持久化该实例从而方便以后使用它的属性值,同时可以随意更改该实例的属性值,推荐在给实例初始化的同时直接使用归档进行存储。       

    (4)如何使用?

              遵循NSCoding协议

    • NSCoding中声明了两个方法,其中一个用于将对象编码到归档中,另一个方法对归档解码来创建一个新对象。
    • 归档时要实现的方法为:-(void)encodeWithCoder:(NSCoder *)aCoder;
    • 可以使用KVC(Key-Value Coding,键值编码)对对象和原生数据类型进行编码和解码。
    • 若要对对象进行归档,必须使用正确的编码方法将所有实例变量编码成encoder。
    • 在解码时,实现一个通过NSCoder解码的对象初始化方法,就可以恢复之前归档的对象。
    • 解码时要实现的方法为:-(id)initWithCoder:(NSCoder *)aDecode;

              实现NSCopying协议

    • 除了要遵循NSCoding协议外,还要求要使用归档的类实现NSCoping协议。 用来复制对象。
    • 这个协议中有一个copywithZone方法:- (id)copyWithZone:(NSZone *)zone;
    • 其实现与inetWitheCoder非常相似,只需创建一个同一类的新实例,然后将新实例的所有属性都设置为与该对象属性相同的值。

         存储 

    NSMutableData *data = [[NSMutableData alloc] init]; //声明一个二进制流 data,开辟了一个空间
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; //声明一个归档类,把归档类的内容放入data中   
    [archiver encodeObject:id forKey:Key]; //用Key进行编码 [archiver finishEncoding]; //结束编码 [data writeToFile:filePath atomically:YES];//编码结束后,归档类的内容已经放入data中了,此时data仍然驻留在内存中,需要写入文件中

          获取

    NSData *data = [[NSMutableData alloc] initWithContentsOfFile:filePath];//用归档文件中的数据初始化
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];//声明一个解归档对象,把data中的数据复制给解归档对象  
    id object = [unarchiver decodeObjectForKey:Key]; //用Key进行解码  
    [unarchiver finishDecoding]; //结束解码 

    (5)有什么缺点?

              当待存储的实例具有成百上千个属性的时候,单纯的一个个去序列化属性值耗时又费力。(当然可以借助runtime机制解决这个缺点,MJExtension这个框架就是这个原理) 

     

    4、Document沙盒存储

    定义:     

              每个iOS应用都有自己的应用沙盒(应用沙盒就是文件系统目录),与其他文件系统隔离。

              应用必须待在自己的沙盒里,其他应用不能访问该沙盒。

              沙盒的本质就是一个文件夹,名字是随机分配的。

    目录:

    • 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)  fristObject];   
         
    // 获取Cache目录       
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) fristObject];    
         
    // 获取Library目录       
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) fristObject]; 
    
    // 获取Tmp目录       
    NSString *path = NSTemporaryDirectory(); 

    作用:

            用来存储和备份稍微较大的不是很重要的数据,比如缓存图片、音频、视频等,最典型的SDWebImage缓存图片的框架。

            当然缓存的时间长短根据开发者选择持久化的目录路径有关。

    问题:

    (1)什么数据适合存储?

        图片、音频、视频、文本等

    (2)存到什么地方?

        写入创建的.txt、.data等任意扩展名的文件中

    (3)使用场景?

        当App中涉及到电子书阅读、听音乐、看视频、刷图片列表等时,推荐使用沙盒存储。

        因为这可以极大的节约用户流量,而且也增强了app的体验效果。       

    (4)如何使用?

              写入文件

    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)  fristObject];             
    NSArray *array = [[NSArray alloc] initWithObjects:@"内容",@"content",nil];            
    NSString *filePath = [path stringByAppendingPathComponent:@"testFile.txt"];            
    [array writeToFile:filePath atomically:YES];

               读取文件

    NSString *path =  [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) fristObject];            
    NSString *filePath = [path stringByAppendingPathComponent:@"testFile.txt"];            
    NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];

    5、本地数据库存储

    定义:

             数据库是一种常用的通过表进行数据存储的方式,表与表之间可以有一对一、一对多的联系,使用外键级联可以达到多表存取数据的目的。

             在iOS中,目前有三种熟知数据库,分别是Sqlite、CoreData、FMDB,用的比较多的是FMDB+Sqlite结合的方式。

    特点:

         Sqlite:

    • 是基于c语言开发的数据库,代码繁琐。
    • 用c语言对数据库执行操作,访问。
    • sqlite是动态的数据库类型,即存储的时候是一种类型,使用的时候可以存储为其他类型。
    • 在OC中不是可视化,不易理解。
    • 建立连接之后可以不关闭连接。

        CoreData;

    • 可视化,且具有undo/redo能力。
    • 可以实现多种文件格式: * NSSQLiteStoreType 、 * NSBinaryStoreType 、* NSInMemoryStoreType 、* NSXMLStoreTyp。
    • 苹果官方API支持,与iOS结合更紧密。

        FMDB;

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

    作用:

        用来进行大数据量的存储工作,不仅仅是容量大,而且通过索引查找速度很快,在App中这个是基本的功能。 

    问题:

    (1)什么数据适合存储?

        开发者定义的类的实例对象,该对象拥有的属性可以是任何类型,例如数字、 字符、 日期、 图片等等。

    (2)存到什么地方? 

        写入创建的.sqlite、.db等任意扩展名的文件中 

    (3)使用场景?

        在App中有大量的数据在没有网络的情况下仍然需要显示时,此时推荐使用本地数据库缓存这些数据。      

    (4)如何使用?

          Sqlite:  

    • 新建项目时,先导入系统框架(C语言). (libsqlite3)
    • 头文件#import<sqlite3.h>
    • sqlite3_open(fileName.UTF8String, &_db); //打开或者创建一个数据 ,*_db自己定义一个sqlite3的成员变量.进行增删改查时要用
    • sqlite3_exec(_db, sql, NULL, NULL,&error);  //不带结果集的语句,只是对表做操作,不会返回出结果,*该函数可进行insert,delete,update操作.
    • sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL);  //做查询前准备,检测SQL语句是否正确.(查询操作select. 带结果集的查询语句,会返回出结果,从表中查询到的数据都会放到stmt结构体中)
    • sqlite3_step(stmt)  //提取查询到的数据,一次提取一条,通过循环可以取出所有数据
    • sqlite3_column_text(stmt, 0) //取出第0列的数据.
    • sqlite3_close(sqlite3 *); //关闭数据库  

      源码

    NSString *sqlStr = @"INSERT OR REPLACE INTO note (cdate,content) VALUES (?,?)";
    sqlite3_stmt *statement;
    //预处理过程,产生结果集
    if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement,
    NULL) == SQLITE_OK)
     {
     NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
        NSString *nsdate = [dateFormatter stringFromDate:model.date];
        //绑定参数开始
        sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL);  sqlite3_bind_text(statement, 2, [model.content UTF8String],
    -1,    NULL);
        //执行插入
    if (sqlite3_step(statement) != SQLITE_DONE)
     { 
    NSAssert(NO, @"插入数据失败。"); }
     }
    }
    //清理结果集,防止内存泄露
    sqlite3_finalize(statement);

          CoreData;

    • 创建项目时,勾选CoreData选项。
    • 此时项目文件中多了一个CoreData___.xcdatamodel文件,选中该文件,进入其创建数据库表格界面,在界面的左下角点击Add Entity添加实体对象,并设置该对象的类名;与此同时,在AppDeletegate类中,自动声明和定义了需要的三个对象Managed Object Model,Persistent Store Coordinator,Managed Object Context ,并且自动封装了大量的sqlite的函数方法。
    • 在attributes选项处添加该实体对象的属性。
    • 选中该实体类,在模拟器选项上点击Editor下的create Managed Object Context subclass..创建Managed Object Context的子类。
    • 这个子类中,编译器自动生成了所添加的所有属性。
    • 在应用程序代理类中用代码对数据库进行操作。 

       源码

    //获取实体对象
    NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:@“ClassName” inManagedObjectContext:self.managedObjectContext];
     
    //创建请求对象  
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“ClassName”]; 
                                
    //创建排序对象
    NSSortDescriptor *ageSort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]
    [request setSortDescriptors:@[ageSort]];
     
    //取出所有的数据
    NSArray *fetcheObjects = [self.managedObjectContext executeFetchRequest:request error:&error]  

          FMDB:

    • 创建或打开

                                 self.db = [FMDatabase databaseWithPath:fileName];  //创建数据库

                                 [self.db open];//打开数据库

    • 查询语句:

            - (FMResultSet *)executeQuery:(NSString*)sql, ... //返回结果集   

                                 - (BOOL)next; //遍历

                                 - { type }ForColumnIndex:(int)columnIdx  //取出某一行对应的具体数据

    • 创建、插入、修改等等语句:

                           - (BOOL)executeUpdate:(NSString*)sql, ... //执行更新 

    • 关闭数据库

                                 [self.db close]; //关闭数据库

         源码

    //<1>使用:(需要FMDatabase *db成员变量)
    //创建或打开:FMDataBase类
    self.db = [FMDatabase databaseWithPath:fileName];
    [self.db open];
    [self.db executeUpdate:@“CREATE TABLE IF NOT EXISTS t_student (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT,age INTEGER)”];
    [self.db executeUpdate:@"INSERT INTO t_student(name , age) VALUES(‘admin’,‘10')];
      
    //<2>查询:FMResultSet类
    //1.查询
    FMResultSet *set = [self.db  executeQuery:@"SELECT * FROM t_student;"]; 
     
    //2.取出数据 即 {type}ForColumnIndex:
    while ([set next])
    {     
      //取出姓名
      NSString *name = [set stringForColumnIndex:1];
      //取出年龄
      int age = [set intForColumnIndex:2];
      NSString *name = [set stringForColumn:@"name"];
      int age = [set intForColumn:@"age"];
      NSLog(@"name = %@, age = %d", name, age);
    }
     
    //<3>关闭数据库
    [self.db close];

    四、选择:

    既然有这五种存储方案,那么在项目中应该选择哪种是最佳的方式呢,这就涉及到了下面这个问题了。

    • 什么时候用?通俗的说,也就是针对某种业务,这五个存储方式的最佳选择。

    针对上面的这个问题,基本是可以有四种参考的维度,分别是:

    • 数据量
    • 数据类型
    • 数据载体的形式
    • 数据操作的特点

    现在就具体的列表说一下这些区别。

    存储方式
    数据量
    数据类型
    数据载体
    数据操作特点
    应用示例
    plist存储 适合存储小数据量而且属于一类的列表类的数据 只能存储固定的几种类型,NSString、NSArray、NSDictionary、NSData、NSDate、NSNumber、Boolean,不能存储自定义对象 非自定义实例对象、基本数据

    直接在可见文件上操作,增删改查很方便

    省市列表、表情列表等

    provincecity.plist.zip

    偏好存储 适合存储小数据量而是一般是配置信息类的数据,有时根据需要也会存储一些标志键值数据

    可以存储OC定义的所有数据类型

    实例对象、基本数据

     

    必须依赖NSUserDefaluts实例对象的实例方法进行存取,过程稍微复杂

    App应用程序的信息配置,如版本号、app名称、用户权限等

    标志键值,如判断启动页、是否自动登录等

    归档存储 适合存储数据量稍微较大的数据 只能存储实现了NSCoding协议和NSCopying协议的实例对象类型。 实例对象 必须依赖NSKeyedUnarchiver、NSKeyedArchiver的类方法或者实例方法进行存取,有时可能还会结合NSUserDefaluts偏好,过程比较复杂 用户的登录/注册信息,如账号、姓名、年龄、学校、省份等
    沙盒存储 适合存储数据量较大的数据 都是存储二进制的NSdata类型 文件File 需要依赖文件管理者NSFileManager将文件写入指定的沙盒目录下、从该目录中再读取文件,过程更复杂一些 图片、音频、视频、文本等,如SDWebImage图片缓存框架
    数据库存储 适合存储大数据量的数据 支持所有的数据类型 实例对象 增删改查方便、快捷,可以任意写sql语句批量处理数据、数据库升级简单等 App中用户浏览过的数据列表内容、电子书读书进度等,基本上大多数app都有本地数据库缓存
  • 相关阅读:
    My97DatePicker使用说明文档
    内存溢出之Tomcat内存配置
    myeclipse控制台不显示tomcat信息
    修改 MyEclipse 编辑区域背景颜色
    window.open() 弹出窗体居中
    javascript控制页面控件隐藏显示的两种方法
    (转)MyEclipse7.5.0版注册码破解及激活操作
    [置顶] Android代码读取 android 设备的电池信息
    [置顶] Android代码获得我们手机的cpu序列号
    [置顶] Android高手进阶教程Android常用名令集锦(图文并茂)!
  • 原文地址:https://www.cnblogs.com/XYQ-208910/p/7687512.html
Copyright © 2020-2023  润新知