数据存储 - iOS中的几种数据存储方式

常见的储存方式

  1. 文件读写存储(plist,NSUserDefaults)
  2. 解归档存储(NSKeyedArchiver)
  3. 数据库存储(SQLite、FMDB、CoreData、Keychain)

了解缓存,先要了解iOS中沙盒机制这个概念

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

IOS应用程序职能在系统为该应用所分配的文件区域下读写文件,这个文件区域就是应用程序沙盒。所有的非代码文件如:图片、声音、映象等等都存放在此。

在mac中command+shift+G命令,然后输入users/用户名/library命令进入库,然后依次进入application support/iphone simulator/版本/applications文件夹,这里面的各个文件夹对应着各个应用程序。

Documents:除了基于NSUserDefaults的首选项设置外,应用程序的数据、文件都保存在该目录下
Library:基于NSUserDefaults的首选项参数保存在Library/Preferences下
tmp:应用程序存储临时文件,ios同步时itunes不会同步这里面的数据,当应用程序不在需要这些文件时,应当删除以避免占用空间。

1. 文件读写存储(NSFileManager)

  • 文件操作可通过单例 NSFileManager 处理。文件存储的路径可以代码设置。

  • 可以存储大量数据,对数据格式没有限制。

  • 但由于数据的存取必须是一次性全部操作,所以在频繁操作数据方面性能欠缺。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ///  路径 如无则自动创建一个
        NSString *stringPath = @"/Users/songzhuo/Desktop/string写入文件.txt";
        NSString *dataPath   = @"/Users/songzhuo/Desktop/data写入文件.txt";
        NSString *arrayPath  = @"/Users/songzhuo/Desktop/array写入文件.plist";

        // 写入
        NSString *string = @"string写文件";
        BOOL isWriteString =[string writeToFile:stringPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
        if (isWriteString) { NSLog(@"string 文件写入成功"); };

        NSData *data =[[NSData alloc]initWithContentsOfFile:stringPath];
        BOOL isWriteData =[data writeToFile:dataPath atomically:YES];
        if (isWriteData) { NSLog(@"data 文件写入成功"); }

        // 数组在写文件时 包含元素只能是 NSString NSArray NSData NSNumber NSDictionary
        NSString *sanNameStr = @"张三";
        NSString *siNameStr = @"李四";
        NSString *wuNameStr = @"王五";
        NSArray *nameArray = [NSArray arrayWithObjects:sanNameStr, siNameStr, wuNameStr,nil];

        BOOL isWriteArray =[nameArray writeToFile:arrayPath atomically:YES];
        if (isWriteArray) { NSLog(@"array 写入文件成功"); }

        /// 读取文件
        NSString *readStr =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@" 读取的string为 : %@", readStr);

        NSString *readDataStr =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@" 读取的data为 :%@", readDataStr);

        NSArray *readArr =[[NSArray alloc]initWithContentsOfFile:arrayPath];
        NSLog(@"读取的array为 :%@",readArr);

        //字典存入plist文件
        NSDictionary *dic =[[NSDictionary alloc]initWithObjectsAndKeys:
                            sanNameStr,@"第一",
                            siNameStr,@"第二",
                            wuNameStr,@"第三",
                            nameArray,@"姓名数组", nil];

        [dic writeToFile:arrayPath atomically:YES];

        NSDictionary *myDic =[[NSDictionary alloc]initWithContentsOfFile:arrayPath];
        NSArray * nameArr =myDic[@"姓名数组"];
        NSLog(@"nameArr : %@",nameArr);
    }
    return 0;
}

当我们正常写完数据之后,后面需要添加新的数据并不能直接像上面那样再次操作。一旦再次操作,会覆盖掉原先的数据。为了避免覆盖,我们需要对添加的数据进行偏移操作。

int main(int argc, const char * argv[]) {
    @autoreleasepool
    {
        NSString *path = @"/Users/songzhuo/Desktop/文件的偏移量.txt";
        NSString*oldStr = @"old string ,";
        [oldStr writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];

        NSFileHandle *wirteFileHandle =[NSFileHandle fileHandleForWritingAtPath:path];

        //添加数据
        NSString *appendString = @"append string";
        NSData *data =[appendString dataUsingEncoding:NSUTF8StringEncoding];

        //设置偏移量   如果不设置偏移量 系统默认从文件一开始添加数据
        [wirteFileHandle seekToEndOfFile]; //设置偏移量跳到文件的末尾
        [wirteFileHandle writeData:data]; //写入数据
        [wirteFileHandle closeFile]; //关闭文件

        //文件定位读取
        // 从中间读取文件一直读取到结尾
        NSFileHandle *readFileHandle =[NSFileHandle fileHandleForReadingAtPath:path];
        if (readFileHandle == nil) {
            NSLog(@"读取失败");
        }

        //获取文件的总长度
        NSFileManager *fileManager =[NSFileManager defaultManager];
        NSDictionary *dic =[fileManager attributesOfItemAtPath:path error:nil];
        NSLog(@"dic : %@",dic);

        NSNumber *number =[dic objectForKey:NSFileSize];
        int  length = [number intValue];
        NSLog(@"length = %d", length);

        //设置文件读取的偏移量
        [readFileHandle seekToFileOffset:length/2];
        NSData *readData =[readFileHandle readDataToEndOfFile];//读到结尾
        //从开始读取文件一直读取到某一位置
        // - (NSData *)readDataOfLength:(NSUInteger)length;

        NSString*readStr =[[NSString alloc]initWithData:readData encoding:NSUTF8StringEncoding];

        NSLog(@"readStr : %@",readStr);
        [readFileHandle closeFile];
    }
    return 0;
}

当存储完数据后,因业务需求,需要改变数据存储的位置,我们就需要对文件进行移动或删除等操作。

int main(int argc, const char * argv[]) {
    @autoreleasepool
    {
        //创建NSFileManager对象 用类方法来创建文件
        NSFileManager * file = [NSFileManager defaultManager];
        NSString *str = @"对一个文件操作";
        //字符串转换为二进制数据
        NSData * data = [str dataUsingEncoding:NSUTF8StringEncoding];

        //创建一个文件并且写入数据
        NSString *home = NSHomeDirectory();
        NSString *path = [home stringByAppendingPathComponent:@"file.txt"];
        [file createFileAtPath:path contents:data attributes:nil];
        NSLog(@"主目录 = %@", home);

        /* ______________创建文件夹_____________   */

        NSString *folderPath = @"/Users/hhg/Desktop/myFolder";
        BOOL isSuccess =[file createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:nil];
        if (isSuccess) {
            NSLog(@"文件夹创建成功");
        }

        /*___________读取文件__________*/
        //读取二进制数据
        NSData *readData =[file contentsAtPath:path];
        //将二进制数据转码
        NSString *readStr =[[NSString alloc]initWithData:readData encoding:NSUTF8StringEncoding];

        NSLog(@"读取文件的内容:  %@",readStr);
        /* __________文件的移动复制剪切______________   */
        //移动
        NSString *movePath = @"/Users/hhg/Desktop/myFolder/file.txt";
        BOOL isMove = [file moveItemAtPath:path toPath:movePath error:nil];

        if (isMove) {
            NSLog(@"文件移动成功");
        }

        //文件复制
        [file copyItemAtPath:movePath  toPath:folderPath error:nil];
        /* _________文件的删除____________*/
        BOOL isDelete =  [file fileExistsAtPath:movePath];
        if ( isDelete ) {
//            [file removeItemAtPath:movePath  error:nil];
        }

        NSDictionary *dic = [file attributesOfItemAtPath:movePath error:nil];
        NSNumber *num = [dic objectForKey:NSFileSize];
        NSLog(@"字典:%@", dic);
        NSLog(@" 尺寸大小:%@", num);
        //-->创建文件夹->创建文件(写入字符串)-> 移动文件 ->读取文件内容->删除文件

    }
    return 0;
}

1.1 plist 格式文件存储

  • plist文件(XML属性列表),在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦
  • 可以存储的类型有NSString,NSDictionary,NSArray,NSNumber,Boolean,NSDate,NSData等基本类型
  • 常用于存储用户的设置,或存储项目中经常用到又不经常改变的数据
  • 创建.plist可以用xcode工具,也可以用代码
  • 不适合存储大量数据,而且只能存储基本类型
  • 可以实现:增,删,改,查等操作,但数据的存取是一次性的全部操作,所以性能方向表现并不好
  • NSDate,BOOL,Int,Float,NSNumber,NSData的数据存储都是转换成NSDictionary的Key-Value形式之后,通过NSDictionary存储方式存储的。
//写入文件
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [doc stringByAppendingPathComponent:@"myself.plist"];
NSDictionary *dict = @{@"name": @"yixiang"};
[dict writeToFile:path atomically:YES];
//读取文件
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];

1.2 NSUserDefaults 沙盒存储(个人偏好设置)

  • NSUserDefaults 沙盒存储(个人偏好存储) 是个单例类,用于存储少量数据,例如登录后的用户名,密码等。
  • 应用程序启动后,会在沙盒路径Library -> Preferences 下默认生成以工程bundle为名的.plist文件,用NSUserDefaults存储的数据都是存储在该.plist文件中。
  • 这种方式本质是操作plist文件,所以性能方面的考虑同plist文件数据储存
//写入文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:@"yixiang" forKey:@"name"];
[defaults setInteger:27 forKey:@"age"];
[defaults synchronize];
//读取文件
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSString *name=[defaults objectForKey:@"name"];
NSInteger age=[defaults integerForKey:@"age"];

2. 解归档存储

  • plist 与 NSUserDefaults(个人偏好设置两种类型的储存只适用于系统自带的一些常用类型,而且前者必须拿到文件路径,后者也只能储存应用的主要信息。
  • 对于开发中自定义的数据模型的储存,我们可以考虑使用归档储存方案。 归档保存数据,文件格式自己可以任意,没有要求
  • 即便设置为常用的数据格式(如:.c .txt .plist 等)要么不能打开,要么打开之后乱码显示。
  • 值得注意的是使用归档保存的自定义模型需要实现NSCoding协议下的两个方法。 不适合存储大量数据,可以存储自定义的数据模型
  • 不适合存储大量数据,可以存储自定义的数据模型。
  • 虽然归档可以存储自定义的数据结构,但在大批量处理数据时,性能上仍有所欠缺。
    YXPerson.h文件如下:
  @interface YXPerson : NSObject<NSCoding>
  @property(nonatomic,copy) NSString *name;
  @property(nonatomic,assign) int age;
  @end

YXPerson.m文件如下:

  #import "YYPerson.h"

  @implementation YYPerson
  -(void)encodeWithCoder:(NSCoder *)aCoder{
      [aCoder encodeObject:self.name forKey:@"name"];
      [aCoder encodeInteger:self.age forKey:@"age"];
  }
  -(id)initWithCoder:(NSCoder *)aDecoder{
      if (self=[super init]) {
          self.name=[aDecoder decodeObjectForKey:@"name"];
          self.age=[aDecoder decodeIntegerForKey:@"age"];
      }
      return self;
  }
  @end

在ViewController中对它进行写入和读取

    //写入对象
    YXPerson *p=[[YXPerson alloc]init];
    p.name=@"yixiang";
    p.age=27;

    NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
    NSString *path=[docPath stringByAppendingPathComponent:@"person.yixiang"];

    [NSKeyedArchiver archiveRootObject:p toFile:path];

     //读取对象
     YXPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path];

3. 数据库存储

3.1. SQLITE数据库
  • 上述三种方法都无法存储大批量的数据,有性能的问题。
    下面简单介绍一下,如何打开数据库,新增一张表格,然后对其进行增删改查的操作。
sqlite3 *_db;
- (void)openDB{
    //获取数据库文件路径
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];

    //将OC字符串转换为c语言的字符串
    const char *cfileName = fileName.UTF8String;

    //打开数据库文件(如果数据库文件不存在,那么该函数会自动创建数据库文件)
    int result = sqlite3_open(cfileName, &_db);
    if (result == SQLITE_OK) {//打开成功
        NSLog(@"成功打开数据库");
    }else{
        NSLog(@"打开数据库失败");
    }
}
- (void)createTable{
    //创建表
    const char *sql = "CREATE TABLE IF NOT EXISTS t_student(id integer PRIMARY KEY AUTOINCREMENT,name text NOT NULL,age integer NOT NULL);";
    char *errmsg= NULL;
    int result = sqlite3_exec(_db, sql, NULL, NULL, &errmsg);
    if (result==SQLITE_OK) {
        NSLog(@"创建表成功");
    }else{
        NSLog(@"创建表失败---%s",errmsg);
    }
}
- (void)insertData{
    //插入数据
    for (int i=0; i<10; i++) {
        //拼接sql语句
        NSString *name = [NSString stringWithFormat:@"yixiangboy--%d",arc4random_uniform(100)];
        int age = arc4random_uniform(20)+10;
        NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_student (name,age) VALUES ('%@',%d);",name,age];

         //执行SQL语句
        char *errmsg = NULL;
        sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
        if (errmsg) {//如果有错误信息
            NSLog(@"插入数据失败--%s",errmsg);
        }else{
            NSLog(@"插入数据成功");
        }
    }
}
- (void)deleteData{
    //删除age小于15的数据
    NSString *sql = [NSString stringWithFormat:@"DELETE FROM t_student WHERE age<15"];
    char *errmsg = NULL;
    sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
    if (errmsg) {
        NSLog(@"删除数据失败");
    }else{
        NSLog(@"删除数据成功");
    }
}
- (void)updateData{
    //大于20岁的都置为20岁
    NSString *sql = [NSString stringWithFormat:@"UPDATE t_student set age=20 WHERE age>20"];
    char *errmsg = NULL;
    sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errmsg);
    if (errmsg) {
        NSLog(@"更新数据失败");
    }else{
        NSLog(@"更新数据成功");
    }
}
- (void)queryData{
    const char *sql = "SELECT id,name,age FROM t_student WHERE age<20";
    sqlite3_stmt *stmt = NULL;

    //进行查询前的准备工作
    if(sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL)==SQLITE_OK){//SQL语句没有问题
        NSLog(@"查询语句没有问题");

        //每调用一次sqlite3_step函数,stmt就会指向下一条记录
        while (sqlite3_step(stmt)==SQLITE_ROW) {//找到一条记录
            //取出数据
            //(1)取出第0个字段的值(int)
            int ID=sqlite3_column_int(stmt, 0);
            //(2)取出第一列字段的值(text)
            const unsigned char *name = sqlite3_column_text(stmt, 1);
            //(3)取出第二列字段的值(int)
            int age = sqlite3_column_int(stmt, 2);

            printf("%d %s %d\n",ID,name,age);
        }
    }else{
        NSLog(@"查询语句有问题");
    }
}

3.2. FMDB
  • 它是一款轻型的嵌入式数据库,安卓和ios开发使用的都是SQLite数据库;占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了;而且它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快。
  • FMDB 正是基于 SQLite 开发的一套开源库。使用时,需要自己写一些简单的SQLite语句
FMDatabase *db;
- (void)openDB {
    //1、获取数据库文件路径
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"];
    
    //2、获取数据库连接
    _db = [FMDatabase databaseWithPath:fileName];

    //3、打开数据库连接
    if ([_db open]) {
        NSLog(@"打开数据库成功");
    }else{
        NSLog(@"打开数据库失败");
    }
}
- (void)createTable{
    BOOL result = [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_student (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
    if (result) {
        NSLog(@"创建表格成功");
    }else{
        NSLog(@"创建表格失败");
    }
}

- (void)insertData{
    for (int i=0; i<10; i++) {
        NSString *name = [NSString stringWithFormat:@"yixiang-%d",arc4random_uniform(100)];
        int age = arc4random_uniform(20)+10;
        BOOL result = [_db executeUpdate:@"INSERT INTO t_student (name, age) VALUES (?, ?);",name, @(age)];
        if (result) {
            NSLog(@"插入成功");
        }else{
            NSLog(@"插入失败");
        }
    }
}
- (void)deleteData{
    BOOL result = [_db executeUpdate:@"DELETE FROM t_student WHERE age<15"];
    if (result) {
        NSLog(@"删除成功");
    }else{
        NSLog(@"删除失败");
    }
}
- (void)updateData{
    BOOL result = [_db executeUpdate:@"UPDATE t_student set age=20 WHERE age>20"];
    if (result) {
        NSLog(@"更新成功");
    }else{
        NSLog(@"更新失败");
    }
}
- (void)queryData{
    FMResultSet *resultSet = [_db executeQuery:@"SELECT * FROM t_student WHERE age > ?",@(20)];
    while ([resultSet next]) {
        int ID = [resultSet intForColumn:@"id"];
        NSString *name = [resultSet stringForColumn:@"name"];
        int age = [resultSet intForColumn:@"age"];
        NSLog(@"%d %@ %d",ID,name,age);
    }
}
3.3. CoreData
  • CoreData 是苹果给出的一套基于 SQLite 的数据存储方案;而且不需要自己写任何SQLite语句。该功能依赖于 CoreData.framework 框架,该框架已经很好地将数据库表和字段封装成了对象和属性,表之间的一对多、多对多关系则封装成了对象之间的包含关系
  • Core Data的强大之处就在于这种关系可以在一个对象更新时,其关联的对象也会随着更新,相当于你更新一张表的时候,其关联的其他表也会随着更新。Core Data的另外一个特点就是提供了更简单的性能管理机制,仅提供几个类就可以管理整个数据库。由于直接使用苹果提供的CoreData容易出错,这里提供一个很好的三方库 MagicalRecord

缓存系统

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

原生接口部分的数据缓存

存储方式:主要采用文件读写、归档、个人偏好设置(NSUserDefaults) 。

具体说明:大部分接口数据解析之后写入文件保存(读写操作最好 GCD 子线程操作);整个应用需要用到的重要数据模型可以考虑采用归档方式(标记状态的数据模型);与用户相关的信息、单个标记标识等采用个人偏好设置。
补充: 原生接口数据存储方式以上三种方式就已够用;当然对于一些涉及查询、删除、更新等操作的数据模型,就需要使用数据库操作。这里推荐使用 CoreData 的封装库 MagicalRecord 。

3.4 Keychain存储

iOS keychain 是一个相对独立的空间,保存到keychain钥匙串中的信息不会因为卸载/重装app而丢失, 。相对于NSUserDefaults、plist文件保存等一般方式,keychain保存更为安全。所以我们会用keyChain保存一些私密信息,比如密码、证书、设备唯一码(把获取到用户设备的唯一ID 存到keychain 里面这样卸载或重装之后还可以获取到id,保证了一个设备一个ID)等等
keychain是用SQLite进行存储的。用苹果的话来说是一个专业的数据库,加密我们保存的数据,可以通过metadata(attributes)进行高效的搜索。keychain适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。

发布了163 篇原创文章 · 获赞 18 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/songzhuo1991/article/details/103424214
今日推荐