美文网首页
iOS常用的数据存储

iOS常用的数据存储

作者: 可能是含钙最高的钙钙 | 来源:发表于2016-08-25 02:08 被阅读38次

    在iOS开发过程中,会经常使用到数据存储.数据存储有好几种常见的方式:plist存储,偏好设置,归档,数据库等,该篇文章是我想做个记录,也想和大家分享下自己的一些见解,涉及的内容不是很深,只做实用性的参考.如果哪位大神对此有深入研究,可以留下来深入讨论下,我请吃饭.

    沙盒
    • iOS的存储都是存在沙盒中的,应用程序只能访问自己的沙盒
    • 沙盒目录里有三个文件:Documents, Library, tmp
      沙盒目录图
    沙盒目录.png
    • Documents:用于存储用户数据或其它应该定期备份的信息
      • 注意,该文件中的内容会在手机连接电脑时进行同步到云上,所以苹果不建议开发者将大量的数据写入到该文件中.如果将视频图片等大量的数据存入到该文件中,会被苹果拒的
    // Documents获取方式
    NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    
    • Library:该目录下有两个子目录,Caches和Preferences.
      • Caches:一般用于存放应用程序专用的支持文件,保存应用程序再次启动所需要的信息
      • 数据存在该文件中
    //Caches获取方法
       NSString *cachesStr = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]
    
    - Preferences:存放应用程序的偏好设置的文件.
    
    • tmp:用于存放临时文件,当程序退出时该文件中的数据会被清空
    //tmp获取方式
     NSString *tmpStr =  NSTemporaryDirectory();
    
    关于沙盒的详细信息,可以去搜下别人的文章,我发现有很多文章对沙盒有详细的讲解的,或者参考苹果官方文档:About Files and Directories
    Plist存储

    定义:plist文件的全名是:Property List,它是一种用来存储串行化后的对象的文件。文件的扩展名是.plist,通常就称为plist文件.主要是用来存储一些OC对象,比如NSArray/NSDictionary,不能存放自定义对象.一般常用的Foundation对象有NSString, NSData, NSDate, NSNumber, NSArray, NSDictionary
    存储原理:写入沙盒.只要有writeToFile的对象,就能进行plist存储,调用writeToFile就能自动生成plist格式的文件
    注意:plist存储,不能存储自定义对象,会失败的。
    plist文件在数据更新的时候需要先取出然后修改在存储
    常用方法:

    // 写入方法
    - (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
    - (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically;
    
    // 取出 字典
    + (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfFile:(NSString *)path;
    + (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfURL:(NSURL *)url;
    
    // 取出 数组
    + (nullable NSMutableArray<ObjectType> *)arrayWithContentsOfFile:(NSString *)path;
    + (nullable NSMutableArray<ObjectType> *)arrayWithContentsOfURL:(NSURL *)url;
    

    获取沙盒路径

    NSString *str = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    

    偏好设置

    在开发中偏好设置用的还是比较多的,存取比较方便,不需要关心文件名,其实底层是利用字典存储一些键值对,你可以存储后打开路径文件看一下.
    存储过程是用NSUserDefaults的单利调用setObject:forKey进行存储.
    注意:不能存储对象,不能及时存储,需要做同步操作,把内存中的数据同步到硬盘上。

    简单使用

     // 存储名字jack到沙盒中,存储的key是 @"name"
        [[NSUserDefaults standardUserDefaults] setObject:@"jack" forKey:@"name"];
        // 同步
        [[NSUserDefaults standardUserDefaults] synchronize];
        // 用key  @"name" 从沙盒中取出名字
        NSString *str =[[NSUserDefaults standardUserDefaults] objectForKey:@"name"];
        
    

    存储方法:

    // 存储方法(set开头)
    - (void)setObject:(nullable id)value forKey:(NSString *)defaultName;
    - (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;
    - (void)setFloat:(float)value forKey:(NSString *)defaultName;
    - (void)setDouble:(double)value forKey:(NSString *)defaultName;
    - (void)setBool:(BOOL)value forKey:(NSString *)defaultName;
    - (void)setURL:(nullable NSURL *)url forKey:(NSString *)defaultName
    

    读取数据的方法:

    // 读取数据的方法
    - (nullable id)objectForKey:(NSString *)defaultName;
    - (nullable NSString *)stringForKey:(NSString *)defaultName;
    - (nullable NSArray *)arrayForKey:(NSString *)defaultName;
    - (nullable NSDictionary<NSString *, id> *)dictionaryForKey:(NSString *)defaultName;
    - (nullable NSData *)dataForKey:(NSString *)defaultName;
    - (nullable NSArray<NSString *> *)stringArrayForKey:(NSString *)defaultName;
    - (NSInteger)integerForKey:(NSString *)defaultName;
    - (float)floatForKey:(NSString *)defaultName;
    - (double)doubleForKey:(NSString *)defaultName;
    - (BOOL)boolForKey:(NSString *)defaultName;
    - (nullable NSURL *)URLForKey:(NSString *)defaultName NS_AVAILABLE(10_6, 4_0);
    

    删除数据:

    // 删除数据
    - (void)removeObjectForKey:(NSString *)defaultName;
    
    归档

    个人理解,归档的过程就是一个序列化的过程,然后存储到指定的路径路径文件.归档可以存储对象,需要遵守NSCoding协议,然后重写下面两种方法

    - (void)encodeWithCoder:(NSCoder *)aCoder;
    - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER
    
    具体使用方法:
    • 自定义模型
    Person类的.h文件
    
    #import <UIKit/UIKit.h>
    
    @interface Person : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) CGFloat height;
    @property (nonatomic, assign) NSInteger age;
    
    @end
    
    Person类的.m文件
    
    #import "Person.h"
    @interface Person ()<NSCoding>
    
    @end
    
    @implementation Person
    
    // 解档
    // 从一个给定unarchiver的数据中返回一个初始化对象。
    - (instancetype)initWithCoder:(NSCoder *)decoder{
        
        if (self = [super init]) {
            
            self.name = [decoder decodeObjectForKey:@"name"];
            self.age = [decoder decodeIntegerForKey:@"age"];
            self.height = [decoder decodeFloatForKey:@"height"];
            
        }
        return self;
    }
    // 归档
    /**
     通过一个给定的archiver把消息接收者进行编码。
     当接收到encodeObject消息的时候,类终端encodeWithCoder方法被调用。
     */
    - (void)encodeWithCoder:(NSCoder *)coder{
        
        [coder encodeObject:self.name forKey:@"name"];
        [coder encodeInteger:self.age forKey:@"age"];
        [coder encodeFloat:self.height forKey:@"height"];
        
    }
    
    • 第一种用法
    // 创建对象
        Person *person = [Person new];
        person.name = @"jack";
        person.age = 20;
        person.height = 180.0f;
    
    // 获取路径
        NSString *str = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"123.archiver"];
    // 归档
       BOOL flag = [NSKeyedArchiver archiveRootObject:person toFile:filePath];
        if (flag) {
            NSLog(@"归档成功");
        }else{
            NSLog(@"归档失败");
        }
    
    // 解档
       Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:str];
    
    • 第二种用法
    // 创建对象
        Person *person = [Person new];
        person.name = @"jack";
        person.age = 20;
        person.height = 180.0f;
    
        //  归档成二进制 (可以用偏好设置保存或者直接写入到沙盒)
        NSData *dataArc = [NSKeyedArchiver archivedDataWithRootObject:person];
       [[NSUserDefaults standardUserDefaults] setObject:dataArc forKey:@"archiver"];
       [[NSUserDefaults standardUserDefaults] synchronize];
    
      // 解档
        NSData *dataUn = [[NSUserDefaults standardUserDefaults] objectForKey:@"archiver"];
        Person *unPerson = [NSKeyedUnarchiver unarchiveObjectWithData:dataUn];
        
    
    • 第三种用法
        // 获取路径
        NSString *str = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"123.archiver"];
        // 创建一个二进制对象
        NSMutableData *data = [[NSMutableData alloc] init];
        // 设置归档写入对象
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
        // 归档
        [archiver encodeObject:person forKey:@"archiver"];// archivingDate的encodeWithCoder方法被调用
        [archiver finishEncoding];
        // 保存二进制到沙盒中
        [data writeToFile:str atomically:YES];
    
        // 解档
        // 通过文件获取一个二进制对象
        NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile:str];
        // 读取二进制文件
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        // 获得类
        Person *person = [unarchiver decodeObjectForKey:@"person"]; ;// initWithCoder方法被调用
        [unarchiver finishDecoding];
        
    

    在自定义模型的.m文件中,重写 - (instancetype)initWithCoder:(NSCoder *)decoder 时,调用的是[super init], 而不是 [super initWithCoder:decoder]; 这是因为Person类继承的是NSObject,NSObject类没有遵守NSCoding这个协议.
    initWithCoder原理:只要解析文件就会调用,xib,storyboard都是文件,因此只要解析这两个文件,就会调用initWithCoder。
    因此如果在storyboard使用自定义view,重写initWithCoder方法,一定要调用[super initWithCoder:],因为只有系统才知道怎么解析storyboard,如果没有调用,就解析不了这个文件。
    重写NSCoding协议的方法时有一个不便的地方就是,当自定义模型特别多的时候,每个.m文件中都需要重写一遍,挺麻烦的.下面这个文件是通过运行时的方法遍历模型中的属性进行设置的,在使用的时候,需要导入#import "GGArchiverCoding.h", #import <objc/runtime.h>头文件,在.m文件中写入这句话就可以了:NSCodingImplementation;

    GGArchiverCoding.h 文件
    注意:该文件可以原版copy的
    
    #define NSCodingImplementation \
    - (instancetype)initWithCoder:(NSCoder *)aDecoder{\
        if (self = [super init]) { \
            unsigned int count; \
            Ivar *ivarList = class_copyIvarList([self class], &count);\
            for (int i = 0; i < count; i++) {\
                Ivar ivar = ivarList[i];\
                const char *ivarName = ivar_getName(ivar);\
                NSString *key = [NSString stringWithUTF8String:ivarName];\
                id value = [aDecoder decodeObjectForKey:key]; \
                [self setValue:value forKey:key];\
            }\
        }\
        return self;\
    }\
    - (void)encodeWithCoder:(NSCoder *)aCoder{ \
        unsigned int count;\
        Ivar *ivarList = class_copyIvarList([self class], &count);\
        for (int i = 0; i < count; i++) {\
            Ivar ivar = ivarList[i];\
            const char *ivarName = ivar_getName(ivar);\
            NSString *key = [NSString stringWithUTF8String:ivarName];\
            id value = [self valueForKeyPath:key];\
            [aCoder encodeObject:value forKey:key];\
        }\
    }
    

    iOS中最常见的三种存储方式介绍完了,虽然使用率还是蛮好的,对于大型数据的存储上面三种就做不到了,或者说想做的很麻烦的.数据库就能这解决这种问题

    SQLite介绍(PS:下面只是对SQLite3的简单实用的介绍,没有做详细的讲解,事后详情再补上.如果哪位大神有详细介绍的文章,可以跟帖链接,共同学习)

    实际开发过程中,很少会直接用SQLite3,常常会采用一些对SQLite3封装好的三方框架,FMDB就是一个非常优秀的框架.
    (度娘介绍):SQLite是一款轻型的嵌入式数据库,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了,它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快,数据库(Database)是按照数据结构来组织、存储和管理数据的仓库.
    想学习数据就,建议安装下Navicat,用着很不错的一款数据库软件.
    数据库可以分为两大种类:关系型数据库(主流)和 对象型数据库
    常用关系型数据库PC端:Oracle、MySQL、SQL Server、Access、DB2、Sybase.嵌入式\移动客户端:SQLite

    数据库的存储结构和excel很像,以表(table)为单位
    数据库的每一列叫字段(column,列,属性)
    每一行叫记录(row,record,每行存放多个字段对应的值)
    建表:Tabel Name: 填写表明,建议以_t开头

    常用的关键字有:select、insert、update、delete、from、create、where、desc、order、by、group、table、alter、view、index等等数据库中不可以使用关键字来命名表、字段

    • SQL语句种类:
      • DDL:数据定义语句:包括create(创表)等操作
      • DML:数据操作语句:包括:insert(增加)、update(修改)、delete(删除)等操作
      • DQL:数据查询语句:最常用的关键字select(查询),别的常用的关键字有where,order by,group by和having
    • 字段类型:SQL将数据划分为四种数据类型
      • integer : 整形值
      • real:浮点值
      • text:文本字符串
      • blob:二进制数据
    • 主键:用来唯一标识某一条记录,在创表的时候用primary key声明一个主键

    创表

    // 在编写数据库语句是,尽可能多的添加一些约束条件,这样可以方便交流
    
    // 导入数据库头文件
    #import <sqlite3.h>
    
    // 保存数据库实例对象
    @property (nonatomic, assign) sqlite3 *db;
    
    
    // 获取沙盒路径
        NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"123.db"];
     // 将OC字符串 转成 C语言字符串
        const char *cFilePath = filePath.UTF8String;
    // 创表
        // 将根据文件路径打开数据库,如果不存在,则会创建一个新的数据库
        // 返回值表示状态
        int result = sqlite3_open(cFilePath, &_db);
      if (result == SQLITE_OK) {
            NSLog(@"成功打开数据库");
        // 创表
            const char *sql = "CREATE TABLE IF NOT EXISTS t_shop (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, price integer NOT NULL);";
            char *erroMsg = NULL;
            // 创表语句  (exec:执行sql语句)
           sqlite3_exec(_db, sql, NULL, NULL, &erroMsg);
            if (erroMsg) {
                NSLog(@"创表失败--%s", erroMsg);
            }
        }else{
             NSLog(@"打开数据库失败");
        }
      // 移动端在操作数据库的时候一般不会考虑关闭
      // 关闭数据库:sqlite3_close(db);
    

    增加数据

        static int i = 0;
        i++;
        NSString *name = [NSString stringWithFormat:@"name-%d", i];
        
        NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_shop (name, price) VALUES ('%@', %d);", name, i];
        char *errMsg;
        sqlite3_exec(self.db, sql.UTF8String, NULL, NULL, &errMsg);
        NSLog(@"错误信息 = %s", errMsg);
    

    删除数据

    // 删除price大于10的数据
        NSString *sql = [NSString stringWithFormat:@"DELETE FROM t_shop where price > 10"];
       char *errMsg;
       sqlite3_exec(self.db, sql.UTF8String, NULL, NULL, NULL);
        NSLog(@"错误信息 = %s", errMsg);
    

    修改数据

    // price小于5,设置为0
        NSString *sql = [NSString stringWithFormat:@"UPDATE t_shop set price = 0 where price < 5"];
        char *errMsg;
        sqlite3_exec(self.db, sql.UTF8String, NULL, NULL, &errMsg);
        NSLog(@"%s", errMsg);
    

    查询数据

     // 查询数据如果用  sqlite3_exec(适合执行没有返回数据的操作)  需要用到第三个参数,函数的回调
    
    
        // 查询所有字段
        const char *sql = "SELECT * FROM t_shop;";
        // 准备
        //  第三个是sql长度, -1自动计算
        // stmt是用来取出查询结果的
        sqlite3_stmt *stmt = NULL;
        int status = sqlite3_prepare_v2(self.db, sql, -1, &stmt, NULL);
        if (status == SQLITE_OK) { // 准备成功 -- SQL语句正确
            
            // sqlite3_step  步骤(一步一步的执行)
            while (sqlite3_step(stmt) == SQLITE_ROW) { // 成功取出一条数据
                // 后面的数字是列号
                // 列好如果为0,代表的是主键的那列
                const char *name = (const char *)sqlite3_column_text(stmt, 1);
                const char *price = (const char *)sqlite3_column_text(stmt, 2);
                
                NSLog(@"name = %s, price = %s", name, price);
            }
        }
        
    
    FMDB简单用法

    FMDB是以0C的方式封装了SQLite的C语言,用起来更加的面相对象,但是SQL语句还是需要写的.这比起单纯的使用SQLite3去做数据存储已经方便了很多,而且FMDB还对数据的存取操作做了线程安全的保证.
    下载下来后的FMDB目录结构

    FMDB.png

    在实际操作过程中,主要用到三个类
    FMDatabase:一个FMDatabase对象就代表一个单独的SQLite数据库
    FMResultSet:查询后的结果集.
    FMDatabaseQueue:用于在多个线程中执行多个查询或更新,保证线程安全的.

    • 简单用法
      创建FMDatabase对象(打开数据库)
    // FMDB提供了两个方法去创建FMDatabase对象
    // 通过指定数据库文件路径来创建FMDatabase对象
    + (instancetype)databaseWithPath:(NSString*)inPath;
    - (instancetype)initWithPath:(NSString*)inPath;
    // path文件路径
    FMDatabase *db = [FMDatabase databaseWithPath:path];
    if (![db open]) {
        NSLog(@"数据库打开失败!");
    }
    
    // 关闭数据库
    // [db close];
    
    文件路径有三种情况(在FMDB文档里有说明的)
    1,具体的系统文件路径,如果不存FMDatabase对象在会自动创建
    2,数据库路径为空字符串@"", 会在零食目录创建一个空的数据库,当FMDatabase关闭连接时,数据库文件也会被删除
    3,nil,会创建一个内存中临时数据库,当FMDatabase关闭连接时,  数据库也会被销毁.
    
    

    执行更新

    在FMDB中,除查询以外的所有操作,都称为“更新”create、drop、insert、update、delete等
    // 下面这些方法在FMDatabase.h文件有详细的说明,建议查看头文件.
    使用executeUpdate:方法执行更新,sql和format是数据库字符串
    - (BOOL)executeUpdate:(NSString*)sql, ...
    - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
    - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
    // 示例
    // 创表
    NSString *creatTableSql = @"CREATE TABLE IF NOT EXISTS t_userTable (id integer PRIMARY KEY autoincrement,  name text, price integer)";
    [db executeUpdate:creatTableSql];
    // 增加数据
    [db executeUpdate:@"UPDATE t_shop SET price = ? WHERE name = ?;", @20, @"Jack"] 
    

    执行查询

    查询方法在FMDatabase.h文件中,返回的是FMResultSet结果集
    常用的查询方法,sql和format是数据库字符串
    - (FMResultSet *)executeQuery:(NSString*)sql, ...
    - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
    - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
    // 示例
    // 查询数据
    FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_shop"];
    // 遍历结果集
    while ([rs next]) {
        NSString *name = [rs stringForColumn:@"name"];
        int price = [rs intForColumn:@"price"];
    } 
    
    
    // FMDatabaseQueue提供了三个对象方法
    //1. 同步队列执行数据库操作。
    - (void)inDatabase:(void (^)(FMDatabase *db))block;
    //2.使用事务进行同步队列执行数据库操作
    - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
    //3.使用递延交易
    - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
    

    在项目中用FMDB做数据的存取的时候需要考虑到线程的安全问题,所以我在使用的时候是这样用的

    
    //  数据库队列
    static FMDatabaseQueue *queue = nil;
    
        if (!queue) {
            NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"fmdb.db"];
            // 该方法会根据路径去获取一个队列,如果路径不存在会自动创建一个数据库
            queue = [FMDatabaseQueue databaseQueueWithPath:path];
        }
        
        if (queue == nil) {
            NSLog(@"创建队列失败");
            return ;
        }
    
       // 创表
        [queue inDatabase:^(FMDatabase *db) {
            NSString *creatTableSql = @"CREATE TABLE IF NOT EXISTS t_userTable (id integer PRIMARY KEY autoincrement, uid integer, name text, timeStamp integer)";
           [db executeUpdate:creatTableSql];
        }];
    
    // 添加数据
        static int i = 0;
        time_t now;
        time_t timeStamp = time(&now);
        i++;
        [queue inDatabase:^(FMDatabase *db) {
            [db executeUpdateWithFormat:@"INSERT INTO t_userTable (uid, name, timeStamp) VALUES (%d, %@, %ld)", i, @"jack", timeStamp];
        }];
        
    // 查询数据
        [queue inDatabase:^(FMDatabase *db) {
            NSString *query = [NSString stringWithFormat:@"SELECT * FROM t_userTable"];
            FMResultSet *set = [db executeQuery:query];
            while ([set next]) {
                NSString *uid = [set stringForColumn:@"uid"];
                NSString *name = [set stringForColumn:@"name"];
                long int timeStamp = [set intForColumn:@"timeStamp"];
            }
        }];
    
    // 修改数据
        NSString *update = [NSString stringWithFormat:@"UPDATE t_userTable set uid = 100 where uid > 10"];
        [queue inDatabase:^(FMDatabase *db) {
            [db executeUpdate:update];
        }];
    
    // 删除数据
        NSString *delete = [NSString stringWithFormat:@"DELETE FROM t_userTable where uid < %zd", 10];
        [queue inDatabase:^(FMDatabase *db) {
            [db executeUpdate:delete];
        }];
    
    

    关于数据库的详细的知识,后期不忙了再补上.大神们如有详细见解,期望您

    相关文章

      网友评论

          本文标题:iOS常用的数据存储

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