zoukankan      html  css  js  c++  java
  • iOS数据持久化存储

    本文中的代码托管在github上:https://github.com/WindyShade/DataSaveMethods 
    相对复杂的App仅靠内存的数据肯定无法满足,数据写磁盘作持久化存储是几乎每个客户端软件都需要做的。简单如“是否第一次打开”的BOOL值,大到游戏的进度和状态等数据,都需要进行本地持久化存储。这些数据的存储本质上就是写磁盘存文件,原始一点可以用iOS本身支持有NSFileManager这样的API,或者干脆C语言fwrite/fread,Cocoa Touch本身也提供了一些存储方式,如NSUserDefaults,CoreData等。总的来说,iOS平台数据持久存储方法大致如下所列:

    • Raw File APIs
    • UserDefault
    • NSCoding => NSKeyedArchived
    • Plist File
    • SQLite(使用C语言)
    • CoreData

    一、Raw File APIs

    ObjC是C的一个超集,所以最笨的方法我们可以直接用C作文件读写来实现数据存储:
    1. 写入文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // File path
    const char * pFilePath = [_path cStringUsingEncoding:NSUTF8StringEncoding];
     
    // Create a new file
    FILE * pFile = fopen(pFilePath, "w+");
     
    if (pFile == NULL) {
        NSLog(@"Open File ERROR!");
        return;
    }
     
    const char * content = [_textField.text cStringUsingEncoding:NSUTF8StringEncoding];
    fwrite(content, sizeof(content), 1, pFile);
    fclose(pFile);

    2. 读取文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // File path
    const char * pFilePath = [_path cStringUsingEncoding:NSUTF8StringEncoding];
     
    // Create a new file
    FILE * pFile = fopen(pFilePath, "r+");
     
    if (pFile == NULL) {
        NSLog(@"Open File ERROR!");
        return;
    }
     
    int fileSize = ftell(pFile);
    NSLog(@"fileSize: %d", fileSize);
     
    char * content[20];
     
    fread(content, 20, 20, pFile);
     
    NSString * aStr = [NSString stringWithFormat:@"%s", &content];
     
    if (aStr != nil && ![aStr isEqualToString:@""]) {
        _textField.text = aStr;
    }
     
    fclose(pFile);

    二、NSUserDefaults

    但是既然在iOS平台作开发,我们当然不至于要到使用C的原生文件接口这种地步,下面就介绍几种iOS开发中常用的数据本地存储方式。使用起来最简单的大概就是Cocoa提供的NSUserDefaults了,Cocoa会为每个app自动创建一个数据库,用来存储App本身的偏好设置,如:开关音效,音量调整之类的少量信息。NSUserDefaults是一个单例,生命后期由App掌管,使用时用 [NSUserDefaults standardUserDefaults] 接口获取单例对象。NSUserDefaults本质上是以Key-Value形式存成plist文件,放在App的Library/Preferences目录下,对于已越狱的机器来说,这个文件是不安全的,所以**千万不要用NSUserDefaults来存储密码之类的敏感信息**,用户名密码应该使用**KeyChains**来存储。

    1.写入数据

    1
    2
    3
    4
    5
    6
    7
    // 获取一个NSUserDefaults对象
    NSUserDefaults * aUserDefaults = [NSUserDefaults standardUserDefaults];
    // 插入一个key-value值
    [aUserDefaults setObject:_textField.text forKey:@"Text"];
     
    // 这里是为了把设置及时写入文件,防止由于崩溃等情况App内存信息丢失
    [aUserDefaults synchronize];

    2.读取数据

    1
    2
    3
    NSUserDefaults * aUserDefaults = [NSUserDefaults standardUserDefaults];
                // 获取一个key-value值
    NSString * aStr = [aUserDefaults objectForKey:@"Text"];

    使用起来很简单吧,它的接口跟 NSMutableDictionary 一样,看它的头文件,事实上在内存里面也是用dictionary来存的。写数据的时候记得用 synchronize 方法写入文件,否则 crash了数据就丢了。

    三、Plist

    上一节提到NSUserDefaults事实上是存成Plist文件,只是Apple帮我们封装好了读写方法而已。NSUserDefaults的缺陷是存储只能是Library/Preferences/<Application BundleIdentifier>.plist 这个文件,如果我们要自己写一个Plist文件呢? 使用NSFileManger可以很容易办到。事实上Plist文件是XML格式的,如果你存储的数据是Plist文件支持的类型,直接用NSFileManager的writToFile接口就可以写入一个plist文件了。 ### Plist文件支持的数据格式有: NSString, NSNumber, Boolean, NSDate, NSData, NSArray, 和NSDictionary. 其中,Boolean格式事实上以[NSNumber numberOfBool:YES/NO];这样的形式表示。NSNumber支持float和int两种格式。

    读写Plist文件

    1. 首先创建plist文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
          // 文件的路径
          NSString * _path = [[NSTemporaryDirectory() stringByAppendingString:@"save.plist"] retain];
          // 获取一个NSFileManger
    NSFileManager * aFileManager = [NSFileManager defaultManager];
    if (![aFileManager fileExistsAtPath:_path]){
          // 文件不存在,创建之
          NSMutableDictionary * aDefaultDict = [[NSMutableDictionary alloc] init];
                                  // 插入一个值,此时数据仍存在内存里
          [aDefaultDict setObject:@"test" forKey:@"TestText"];
     
                                  // 使用NSMutableDictionary的写文件接口自动创建一个Plist文件
          if (![aDefaultDict writeToFile:_path atomically:YES]) {
              NSLog(@"OMG!!!");
          }
     
          [aDefaultDict release];
      }

    2. 写入文件

    1
    2
    3
    4
    5
    6
            // 写入数据
    NSMutableDictionary * aDataDict = [NSMutableDictionary dictionaryWithContentsOfFile:_path];
    [aDataDict setObject:_textField.text forKey:@"TestText"];
        if (![aDataDict writeToFile:_path atomically:YES]) {
            NSLog(@"OMG!!!");
        }

    3. 读取文件

    1
    2
    3
    4
    5
         NSMutableDictionary * aDataDict = [NSMutableDictionary dictionaryWithContentsOfFile:_path];
    NSString * aStr = [aDataDict objectForKey:@"TestText"];
    if (aStr != nil && aStr.length > 0) {
        _textField.text = aStr;
    }

    四、NSCoding + NSKeyedArchiver

    上面介绍的几种方法中,直接用C语言的接口显然是最不方便的,拿出来的数据还得自己进行类型转换。NSUserDefaults和Plist文件支持常用数据类型,但是不支持自定义的数据对象,好像Cocoa提供了NSCoding和NSKeyArchiver两个工具类,可以把我们自定义的对象编码成二进制数据流,然后存进文件里面,下面的Sample为了简单我直接用cocoa的接口写成plist文件。 如果要使用这种方式进行存储,首先自定义的对象要继承NSCoding的delegate。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
            @interface WSNSCodingData : NSObject<NSCoding>
     
    然后继承两个必须实现的方法encodeWithCoder:和initWithCoder:
     
            - (void)encodeWithCoder:(NSCoder *)enoder {
                [enoder encodeObject:data forKey:kDATA_KEY];
            }
     
            - (id)initWithCoder:(NSCoder *)decoder {
                data = [[decoder decodeObjectForKey:kDATA_KEY] copy];
                            return [self init];
            }

    这里data是我自己定义的WSNSCodingData这个数据对象的成员变量,由于数据在使用过程中需要持续保存在内存中,所以类型为copy,或者retain也可以,记得在dealloc函数里面要realease。这样,我们就定义了一个可以使用NSCoding进行编码的数据对象。

    保存数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    - (void)saveData {
        if (aData == nil) {
            aData = [[WSNSCodingData alloc] init];
        }
     
        aData.data = _textField.text;
     
        NSLog(@"save data...%@", aData.data);
                    // 这里init的NSMutableData是临时用来存储数据的
        NSMutableData   * data = [[NSMutableData alloc] init];
                    // 这个NSKeyedArchiver则是进行编码用的
        NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
        [archiver encodeObject:aData forKey:DATA_KEY];
        [archiver finishEncoding];
                    // 编码完成后的NSData,使用其写文件接口写入文件存起来
        [data writeToFile:_path atomically:YES];
        [archiver release];
        [data release];
     
        NSLog(@"save data: %@", aData.data);
    }

    读取数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    - (void)loadData {
        NSLog(@"load file: %@", _path);
        NSData * codedData = [[NSData alloc] initWithContentsOfFile:_path];
        if (codedData == nil) return;
     
                    // NSKeyedUnarchiver用来解码
        NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData];
                    // 解码后的数据被存在一个WSNSCodingData数据对象里面
        aData = [[unarchiver decodeObjectForKey:DATA_KEY] retain];
        [unarchiver finishDecoding];
        [unarchiver release];
     
        [codedData release];
     
        if (aData.data != nil) {
            _textField.text = aData.data;
        }
    }

    所以其实使用NSCoding和NSKeyedArchiver事实上也是写plist文件,只不过对复杂对象进行了编码使得plist支持更多数据类型而已。

    五、 SQLite

    如果App涉及到的数据多且杂,还涉及关系查询,那么毋庸置疑要使用到数据库了。Cocoa本身提供了CoreData这样比较重的数据库框架,下一节会讲到,这一节讲一个轻量级的数据库——SQLite。 SQLite是C写的的,做iOS开发只需要在工程里面加入需要的框架和头文件就可以用了,只是我们得用C语言来进行SQLite操作。 关于SQLite的使用参考了这篇文章:http://mobile.51cto.com/iphone-288898.htm但是稍微有点不一样。

    1. 在编写SQLite代码之前,我们需要引入SQLite3头文件:

    1
    #import <sqlite3.h>

    2. 然后给工程加入 libsqlite3.0.dylib 框架。 3. 然后就可以开始使用了。首先是打开数据库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    - (void)openDB {
        NSArray * documentsPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory
                                                                    , NSUserDomainMask
                                                                    , YES);
        NSString * databaseFilePath = [[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:@"mydb"];
     
        // SQLite存的最终还是文件,如果没有该文件则会创建一个
        if (sqlite3_open([databaseFilePath UTF8String], &_db) == SQLITE_OK) {
            NSLog(@"Successfully open database.");
            // 如果没有表则创建一个表
            [self creatTable];
        }
    }

    3.关闭数据库,在dealloc函数里面调用:

    1
    2
    3
    - (void)closeDB {
        sqlite3_close(_db);
    }

    4.创建一个表:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    - (void)creatTable {
        char * errorMsg;
        const char * createSql="create table if not exists datas (id integer primary key autoincrement,name text)";
     
        if (sqlite3_exec(_db, createSql, NULL, NULL, &errorMsg) == SQLITE_OK) {
            NSLog(@"Successfully create data table.");
        }
        else {
            NSLog(@"Error: %s",errorMsg);
            sqlite3_free(errorMsg);
        }
    }

    5. 写入数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - (void)saveData {
        char * errorMsg;
            // 向 datas 表中插入 name = _textFiled.text 的数据
        NSString * insertSQL = [NSString stringWithFormat:@"insert into datas (name) values('%@')", _textField.text];
     
            // 执行该 SQL 语句
        if (sqlite3_exec(_db, [insertSQL cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL, &errorMsg)==SQLITE_OK) {
            NSLog(@"insert ok.");
        }
    }

    6. 读取数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    - (void)loadData {
        [self openDB];
     
        const char * selectSql="select id,name from datas";
        sqlite3_stmt * statement;
        if (sqlite3_prepare_v2(_db, selectSql, -1, &statement, nil)==SQLITE_OK) {
            NSLog(@"select ok.");
        }
     
        while (sqlite3_step(statement) == SQLITE_ROW) {
            int _id = sqlite3_column_int(statement, 0);
            NSString * name = [[NSString alloc] initWithCString:(char *)sqlite3_column_text(statement, 1) encoding:NSUTF8StringEncoding];
            NSLog(@"row>>id %i, name %@",_id,name);
     
            _textField.text = name;
        }
     
        sqlite3_finalize(statement);
    }

    五、CoreData

    大型数据存储和管理。 XCode自带有图形化工具,可以自动生成数据类型的代码。 最终存储格式不一定存成SQLite,可以是XML等形式。 (未完待续。。。)

  • 相关阅读:
    Spring Cloud Alibaba | Nacos配置管理
    Spring Cloud Alibaba | Nacos服务注册与发现
    Spring Cloud Alibaba | Nacos服务中心初探
    Spring Cloud Alibaba | 序言
    漫谈网站优化提速
    Kafka 0.8 Producer (0.9以前版本适用)
    Kafka——JAVA_API的使用之Producer(核心原理与示例)
    Kafka单线程Consumer及参数详解
    什么是Kafka?
    Kafka学习(一)-------- Quickstart
  • 原文地址:https://www.cnblogs.com/lisa090818/p/3695187.html
Copyright © 2011-2022 走看看