美文网首页UI笔记
iOS开发之数据的持久化存储机制

iOS开发之数据的持久化存储机制

作者: 蛐蛐_ | 来源:发表于2015-12-21 21:34 被阅读216次

    IOS中数据的持久化保存这块内容,类似于Android中文件的几种常见的存储方式。
    对于数据的持久化存储,ios中一般提供了4种不同的机制。
    1.属性列表
    2.对象归档
    3.数据库存储(SQLite3)
    4.苹果公司提供的持久性工具Core Data。

    其实储存的形式无非就这么几种,而我们还必须要关心的是,这些文件会被放置在那个文件下,然后如何读取。
    也就是说:IOS上数据存储,我们要了解的两点,数据存储格式(也就是存储机制),数据存储位置。
    1》文件如何存储(如上面4点)
    2》文件存储在哪里。
    对于数据的操作,其实我们关心的是操作的速率。
    就好比在Adnroid中偏好存储,数据库存储,io存储一样。
    我大致问了我们公司新来的ios哥们,他说他们培训机构基本对数据操作这块就讲了属性列表和数据库,以及普通的文件存储(比如音视频图这些多媒体数据)。
    我就只好先看看书了。

    一:应用文件目录

    首先我们来看了解下ios数据存储位置,因为只有知道位置路径我们才能去读取数据,而数据的持久化机制不过是针对操作速率来考虑的,
    比如我们大致知道属性列表(既键值对形式)的存储熟虑应该高于数据库高于io文件流存储。
    我们在选择用何种机制存储数据,主要也是看数据的形式。

    一个ios应用安装后大致会有如下文件夹及其对应路径:

    在mac上看模拟器中应用路径:

    /Users/nono/Library/Application Support/iPhone Simulator/5.1/Applications/2D135859-1E80-4754-B36D-34A53C521DE3
    

    你在finder中的home下可能找不到Library这个目录,因为貌似是影藏起来了(我这机器上是,在终端可以看到)。
    最后那一窜的类似序列号的东西就是ios自动给应用生成的一组应用唯一识别码最为了应用的home目录名。
    其下面就是上图所示了。
    书上对这些文件夹介绍:

    Document:应用程序将其数据存储在这个文件夹下,基于NSUserDefaults的首选项的设置除外。

    简单理解是,基本上我们要操作的一些数据都是存储在这个文件夹下面的

    TIPS:这边提下一点,对于ios系统这么分配文件夹,是因为在设备进行同步时,ITunes有选择性的意识来备份文件。

    比如我们可以猜到,tmp下的应该就不会备份了。
    对于Document文件夹目录路径的获取,API提供了这么一种方法:

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  
       NSString *docPath = [paths objectAtIndex:0];  
    

    Library:基于NSUserDefault首选项设置存储在其下Preferences文件夹中,简单来说,这个文件夹一般你很少操作到。

    书上对于这部分基本没介绍。估计对于初级部分是跳过了。

    Tmp:应用临时存储文件,当不需要时,应用负责删除其下的文件数据。
    该文件也提供了目录获取方法:

    NSString *tmpDoc = NSTemporaryDirectory();
    

    应用程序文件:这个基本没提到书上,但是我们大致可以猜测,这就是整个应用程序的程序文件夹吧。

    好了,以上我们大致解决了我们提到的第一个点,文件存储目录

    二:数据存储机制

    1.属性列表 Write写入方式

    这个其实我们早见过,plist就是,感觉用来存储键值对小数据是最合适,因为速率很高。

    这个存储机制很简单,对于前面我们使用过了在plist文件来读取数据填充一些列表,只不过那会plist文件存储位置不同,

    用的是Mainbundle什么的来返回文件夹,其实这边我也推测,上面提到有个应用程序文件夹,它下面的文件就是这么来读取的~(反正暂时不管他)

    这边不过就是改变了存储位置,数据操作还是一样的

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  
    NSString *docPath = [paths objectAtIndex:0];  
    NSString *myFile = [docPath stringByAppendingPathComponent:@"my.list"];  
    //读取文件  
    NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile];  
    //操作完若修改了数据则,写入文件  
    [array writeToFile:myFile atomically:YES]; 
    

    2.对象归档
    上面的属性列表存储机制,我们都知道,这个机制支持NSArray,NSDictionary,NSData,NSString,NSNumber,NSDate 等等

    这些对象直接写入plist文件中。

    那么对于一些复杂对象,我要保存整个这个对象数据呢?

    反正我是这么觉得,这个机制很像java中的对象整体序列化。当然,这些数据在读取是就需要遵循一种墨守成规的协议了。

    首先我们定义的对象类,必须实现NSCoding和NSCopying协议(额,网上说后面这个不实现也可以,我猜是他对象没有copy操作,因此没出错)书本上反正是实现了这两个协议
    然后归档中用到的操作类
    NSKeyedArchiver
    这边我们定义一个对象,h文件中定义两属性,申明要实现的NSCoding和NSCopying协议

    Person.h

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject <NSCoding>
    
    @property (nonatomic,retain)NSString *gender; // 性别
    @property (nonatomic,retain)NSString *name; // 姓名
    @property (nonatomic,assign)int age; // 年龄
    @end
    

    Person.m

    #import "Person.h"
    
    @implementation Person
    
    
    // 归档 实际上就是将当前类的属性编码为NSData类型
    - (void)encodeWithCoder:(NSCoder *)aCoder{
        
        // 实际编码过程,原理就是将name这个属性的值编码为NSData类型,因为我们解码的时候,需要重新为该类属性赋值,所以需要加标记,也就是Key。
        [aCoder encodeObject:self.name forKey:@"name"];
        [aCoder encodeObject:self.gender forKey:@"gender"];
        [aCoder encodeInt:self.age forKey:@"age"];
        NSLog(@"执行了归档的方法");
    }
    
    // 反归档  因为归档的过程中,我们是将当前类转换为NSData类型,并且储存到了某个文件中,当我们从文件中读取出来数据的时候,基础类型,例如:NSArray等都有initWithContentsOfFile的方法来初始化,但是复杂类型没有类似的方法,只能是反归档来完成此事。
    - (instancetype)initWithCoder:(NSCoder *)aDecoder{
        
        if (self = [super init]) {
            // 将刚才编码为NSData类型的属性,又通过解码方式变回原来的类型,上面编码过程中,所赋给的key值为何种名称,底下的解码得对应上。
            self.name = [aDecoder decodeObjectForKey:@"name"];
            self.age = [aDecoder decodeIntForKey:@"age"];
            self.gender = [aDecoder decodeObjectForKey:@"gender"];
             NSLog(@"执行了反归档的方法");
        }
        return self;
    }
    @end
    
    

    RootViewController.m

    #import "RootViewController.h"
    #import "SandBoxPaths.h"
    #import "Person.h"
    @interface RootViewController ()
    
    @end
    
    @implementation RootViewController
    
    
    #pragma mark -------- 归档
    // 归档并存入沙盒中
    - (void)archiverAndSaveSandBox{
        // 归档实际上就是将Person对象转换为NSData类型的数据
        
        
        Person *person = [[Person alloc]init];
        person.name = @"厦航";
        person.age = 24;
        person.gender = @"女";
        
        //  归档的时候,实际是将复杂类对象的属性一一转换为NSData类型,所以是逐步转换的,最终需要将每一步转换好的NSData类型组装为一个完整的NSData,所以我们需要一个可变的NSData来接收它
        NSMutableData *receiveData = [[NSMutableData alloc]init];
        //=========================================================================
        // 归档操作需要借助系统的一个归档工具类来实现,这个类实际的操作就是将Person对象转换为NSData类型的数据,并赋值给刚才咱们初始化的NSData对象
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:receiveData];
        //=========================================================================
        // 归档开始
        [archiver encodeObject:person forKey:@"person"];
        // 需要有一个标志,让我们知道归档完成了,我们的receiveData中有值了 (不然会出错误)
        [archiver finishEncoding];
        
        // 已经转换完成的,就可以进行数据持久化了
        NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
        
        [receiveData writeToFile:pathString atomically:YES];
        NSLog(@"-------%@",pathString);
        
    }
    
    #pragma mark ----- 反归档
    - (void)unArichiver{
        // 反归档,实际上就是将NSData类型转换为复杂类型对象,就是本例中的person对象
        
        NSString *pathString = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"person.DA"];
        NSData *data = [[NSData alloc]initWithContentsOfFile:pathString];
        // 反归档,反归档也需要借助系统的一个反归档工具类来实现
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data];
        // 开始反归档
       Person *person =  [unarchiver decodeObjectForKey:@"person"];
        
        NSLog(@"name-------%@",person.name);
        
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self archiverAndSaveSandBox];
        [self unArichiver];
        
        }
        
    
    

    三.数据库存储

    1.这里必须先要导入一个系统文件。在xcode7.0之后把后缀改了,现在叫libsqlite3.tbd

    //里面有操作sqlite数据库的所有函数,我们要操作数据库,就需要导入libSqlite3的系统库 路径:TARGETS -> Build Phases -> Link Binary with Libraries
    

    导入头文件

    #import <sqlite3.h>  //里面有操作sqlite数据库的所有函数,我们要操作数据库,就需要导入libSqlite3的系统库 路径:TARGETS -> Build Phases -> Link Binary with Libraries
    #import "SandBoxPaths.h"
    
    @implementation RootViewController
    #pragma mark ------ 打开或者创建数据库
    // 打开或者创建数据库
    - (sqlite3 *)openOrCreateDB{
        // 首先我们需要一个保存数据库的文件路径
        NSString *dbPath = [[SandBoxPaths documentsPath]stringByAppendingPathComponent:@"db.sqlite" ];
        // 数据库的句柄
        sqlite3 *sqlite3 = NULL;
        // 如果数据库已经存在,此函数就是打开当前数据库文件,如果该数据库文件不存在,那么此函数就是创建数据库文件并打开。
        // filename:数据库文件的路径
        // ppDb: 数据库句柄此变量的指针的指针。当前数据库创建或者打开成功之后,会将地址指针保存在该参数中,这样,此句柄变量就可以通过指针来操作数据库。
        // 由于此函数为C函数,但是dbPath为OC对象,所以需要将OC字符串转换为C字符串
        // 此函数有返回值,sqlite3中对所有的操作,只要没有返回数据结果集的,都会有一个int的返回值,来标识此操作是否进行成功。
       int result =  sqlite3_open(dbPath.UTF8String, &sqlite3);
        
        if (result == SQLITE_OK) {
            
            NSLog(@"数据库打开成功");
            
            return sqlite3;
        }else{
            NSLog(@"数据库打开失败");
            return NULL;
        }
        
    }
    #pragma mark ---------- 执行无返回结果集的SQL操作
    // 执行无返回结果集的SQL操作
    - (BOOL)exeSqlWithSQLString:(NSString *)sqlStr{
        // 打开数据库
        sqlite3 *sqlDB = [self openOrCreateDB];
        // 执行SQL的函数
        // 第一个参数:数据库的句柄,可以理解为就是数据库
        // 第二个参数:所要执行的sql语句
        // 第三个参数:执行完SQL之后的回调方法。
        // 第四个参数:回调方法的第一个参数
        // 第五个参数:错误日志,等同于OC中的NSError,这里是char类型。
       int result =  sqlite3_exec(sqlDB, sqlStr.UTF8String, NULL, NULL, NULL);
        if (result == SQLITE_OK) {
            NSLog(@"语句执行成功");
            // 关闭数据库
            sqlite3_close(sqlDB);
            return YES;
        }else{
            NSLog(@"语句执行失败");
            // 关闭数据库
            sqlite3_close(sqlDB);
            return NO;
        }
    }
    
    #pragma mark ----- 查询数据库
    // 查询数据库
    - (NSArray *)queryDBWithSqlString:(NSString *)sqlStr{
        
        
        // 初始化一个可变数组,一会用来盛放所有的结果
        NSMutableArray *resultMutableArray = [[NSMutableArray alloc]init];
        // 打开数据库
        sqlite3 *sqlDB = [self openOrCreateDB];
        // 用来保存记录的指针对象
        sqlite3_stmt *stament = NULL;
        // 用来检查SQL的函数,如果SQL语句编译无问题,就将编译好的SQL保存到stament中
       int result =  sqlite3_prepare(sqlDB, sqlStr.UTF8String, -1, &stament, NULL);
        
        if (result == SQLITE_OK) {
            while (sqlite3_step(stament) == SQLITE_ROW){
                
                // while每执行一次,就会有一条新记录,所以我们每次都需要一个新的字典,也就是初始化好的字典来盛放记录
                NSMutableDictionary *rowDic = [NSMutableDictionary dictionary];
             // 每执行一次step函数,都会在stament中保存一条完整的记录。
                // 取出一条记录中的某个字段
                // 第二个参数是指取出第几列的字段值
                
                int number = sqlite3_column_int(stament, 0);
                [rowDic setObject:[NSNumber numberWithInt:number]forKey:@"number"];
                
            const unsigned char *name = sqlite3_column_text(stament, 1);
                NSString *nameString = [NSString stringWithCString:(const char *)name encoding:NSUTF8StringEncoding];
    //            NSString *nameString = [NSString stringWithUTF8String:(const char *)name];
                [rowDic setObject:nameString forKey:@"name"];
                const unsigned char *gender = sqlite3_column_text(stament, 2);
                NSString *genderString = [NSString stringWithCString:(const char *)gender encoding:NSUTF8StringEncoding];
                [rowDic setObject:genderString forKey:@"gender"];
                
                
                //组装好字典之后,将该字典放入数组
                [resultMutableArray addObject:rowDic];
            }
        }
        
        // 关闭数据库
        sqlite3_close(sqlDB);
        // 释放stament所持有的资源
        sqlite3_finalize(stament);
        return resultMutableArray;
    }
    
    

    这里封装了DataBaseHelper单例可以直接拿来使用。

    四。Core Data存储机制

    大致浏览下基本感觉就是将对象归档搞成了可视化和简单化。

    这块内容比较多。网上资料也挺丰富的。

    暂时不做介绍了。

    总结下:其实对于ios数据存储,最常用和主要要掌握的就是属性列表和数据库,因为两个是出镜率比较高的。

    其他可能在数据存明显体现出储优势时,我们会去考虑用另外两种机制。

    基础的来说,必须掌握属性列表和sqlite的操作存储。

    相关文章

      网友评论

        本文标题:iOS开发之数据的持久化存储机制

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