• iOS数据持久化方式及class_copyIvarList与class_copyPropertyList的区别


    iOS数据持久化方式:
    plist文件(属性列表)
    preference(偏好设置)
    NSKeyedArchiver(归档)
    SQLite3
    CoreData
    沙盒:iOS程序默认情况下只能访问自己的程序目录,这个目录被称为沙盒。
    沙盒目录结构:
    Documents
    Library->Caches Preferences
    tmp
    获取沙盒路径最方便的方法:
    NSString *sandBoxPath = NSHomeDirectory();
    然后在其路径后追加Documents或tmp或Library
    各个目录适合存储的文件:
    Documents目录存储重要文件,iTunes同步时会同步该文件夹内容,一般用来存储数据库文件
    Library->Preferences通常保存应用配置信息,iTunes同步时也会同步该文件夹内容
    Library->Caches:iTunes不会同步该文件夹内容,适合存储体积大,不需要备份的非重要数据。
    tmp:iTunes不会同步该文件夹内容,系统可能在程序没运行时就删除该目录下的文件,所以适合保存应用中的一些临时文件,用完就删除。
    1.plist文件是将某些特定的类,通过XML文件的格式保存在目录中。
    包含以下几个类:
    NSArray
    NSDictionary
    NSString
    NSData
    NSNumber
    NSDate
    这里先抛出一个疑问:比如我先往plist文件里写入一个字典,然后我想接着写入一个数组,但是实际操作中,后面写入的数据总是会覆盖前面写入的数据,有什么解决方案吗?
    2.NSUserDefaults
    一定要记得synchronize,存储的值都保存在Library/Preferences里的以程序bundle id命名的plist文件里。
    3.NSKeyedArchiver,要想使用这种方式存储,则必须遵循NSCoding协议
    @protocol NSCoding
    - (void)encodeWithCoder:(NSCoder *)aCoder;
    - (instancetype)initWithCoder:(NSCoder *)aDecoder;

    我们一般常见的的序列化方式如下图:

    以上是一个简单的编解码示意,但是可能会有一些缺陷:
     1.若需要实现序列化的类有很多属性,那是不是就要写很多代码?
     2.若该类不是直接继承自NSObject,而是有其他父类,那就需要先实现父类的序列化
     这里需要注意:序列化的范围不仅仅是自身类的变量,还要把除NSObject外的所有父类的变量都进行序列化

    其实编解码的核心就是:遍历该类的属性变量、实例变量及其父类的属性变量(父类的实例变量为其私有,我们如果对其进行编解码会崩溃),这时候就用到运行时的api了

    敲重点:

    Class cls = [self class];
    BOOL isSelfClass = (cls == [self class]);

    objc_property_t *propertyList = isSelfClass ? NULL : class_copyPropertyList(cls, &propertyCount);
    Ivar *ivarList =  isSelfClass ? class_copyIvarList(cls, &iVarCount) :NULL;

    解释一下,首先判断是不是本类,如果是本类,就使用class_copyIvarList获取属性变量和实例变量;

    如果是父类,就使用class_copyIvarList获取父类的属性变量。

    //解码
    - (id)initWithCoder:(NSCoder *)coder
    {
        Class cls = [self class];
        while (cls != [NSObject class]) {
            BOOL isSelfClass = (cls == [self class]);
            unsigned int iVarCount = 0;
            unsigned int propertyCount = 0;
            
            objc_property_t *propertyList = isSelfClass ? NULL : class_copyPropertyList(cls, &propertyCount);
            Ivar *ivarList =  isSelfClass ? class_copyIvarList(cls, &iVarCount) :NULL;
            
            unsigned int finalCount = isSelfClass ? iVarCount : propertyCount;
            
            for (int i = 0; i < finalCount; i++) {
                const char * varName = isSelfClass ? ivar_getName(*(ivarList + i)) :property_getName(*(propertyList + i));//取得变量名字,将作为key
                NSString *key = [NSString stringWithUTF8String:varName];
                //decode
                id  value = [coder decodeObjectForKey:key];//解码
                NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
                if (value && [filters containsObject:key] == NO) {
                    [self setValue:value forKey:key];//使用KVC强制写入到对象中
                }
            }
            free(ivarList);//记得释放内存
            free(propertyList);
            cls = class_getSuperclass(cls);
        }
        
        return self;
    
        
    }
    //编码
    - (void)encodeWithCoder:(NSCoder *)coder
    {
        Class cls = [self class];
        while (cls != [NSObject class]) {
            BOOL isSelfClass = (cls == [self class]);
            unsigned int varCount = 0;
            unsigned int properCount = 0;
            Ivar *ivarList = isSelfClass ? class_copyIvarList([self class], &varCount) : NULL;
            objc_property_t *propertyList = isSelfClass ? NULL : class_copyPropertyList(cls, &properCount);
            
            unsigned int finalCount = isSelfClass ? varCount : properCount;
            for (int i = 0; i < finalCount; i++) {
                const char *varName = isSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propertyList + i));
                NSString *key = [NSString stringWithUTF8String:varName];
                id varValue = [self valueForKey:key];//使用KVC获取key对应的变量值
                NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];
                if (varValue && [filters containsObject:key] == NO) {
                    [coder encodeObject:varValue forKey:key];
                }
            }
            free(ivarList);
            free(propertyList);
            cls = class_getSuperclass(cls);
        }
        
    }

    另外,为一个类添加描述也可以同理进行处理,这样就可以很直观的看出容器里装的model类是怎样子的了:

    /* 用来打印本类的所有变量(成员变量+属性变量),所有层级父类的属性变量及其对应的值 */  
    - (NSString *)description
    {   
        NSString  *despStr = @"";   
        Class cls = [self class];   
        while (cls != [NSObject class]) {   
            /*判断是自身类还是父类*/  
            BOOL bIsSelfClass = (cls == [self class]);  
            unsigned int iVarCount = 0; 
            unsigned int propVarCount = 0;  
            unsigned int sharedVarCount = 0;    
            Ivar *ivarList = bIsSelfClass ? class_copyIvarList([cls class], &iVarCount) : NULL;/*变量列表,含属性以及私有变量*/   
            objc_property_t *propList = bIsSelfClass ? NULL : class_copyPropertyList(cls, &propVarCount);/*属性列表*/   
            sharedVarCount = bIsSelfClass ? iVarCount : propVarCount;   
            
            for (int i = 0; i < sharedVarCount; i++) {  
                const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); 
                NSString *key = [NSString stringWithUTF8String:varName];    
                /*valueForKey只能获取本类所有变量以及所有层级父类的属性,不包含任何父类的私有变量(会崩溃)*/  
                id varValue = [self valueForKey:key];   
                NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; 
                if (varValue && [filters containsObject:key] == NO) { 
                    despStr = [despStr stringByAppendingString:[NSString stringWithFormat:@"%@: %@n", key, varValue]]; 
                }   
            }   
            free(ivarList); 
            free(propList); 
            cls = class_getSuperclass(cls); 
        }   
        return despStr; 
    }

    然后把以上方法用宏定义起来,放进一个header.h里,需要对哪个类进行编解码,就把header.h导进去。

    4.sqlite

    说到本博客的关键地方了,以上数据存储方法均是覆盖存储!!!如果想要追加一条数据,就必须把整个文件里的数据读出来,然后修改数据后再把数据写入,所以不适合存储大量数据。这时候sqlite就登场了

    SQLite3:
    可存储数据类型:
    integer:整数
    real:实数(浮点数)
    text:文本字符串
    blob:二进制数据,比如图片,文件之类的

    数据库、删、改、查,就查询是最特殊的,需要一个结果集来承载。
    每次进行任何操作之前,都不要忘了打开数据库和关闭数据库。

    在iOS中要使用SQLite3,需要添加库文件:libsqlite3.tbd,这是一个C语言的库,所以直接使用SQLite3还是比较麻烦的。

    常用API:

    sqlite3_open(打开数据库)、sqlite3_close(关闭数据库)、sqlite3_exec(执行增删改)、sqlite3_prepare_v2(检查sql语句的合法性)、sqlite3_step(逐行获取查询结果,直到最后一条记录)、sqlite3_column_XXX(获取查询结果里的某个字段内容,XXX为表字段类型)

    #pragma mark 创建数据库并创建表
    - (void)createDB {
        NSString *dbPath = [[self getDocumentsPath]stringByAppendingPathComponent:@"person.sqlite"];
        NSInteger openResult = sqlite3_open(dbPath.UTF8String, &_sqlite3);
        if (openResult == SQLITE_OK) {
            NSLog(@"数据库打开成功");
            char *errmsg = NULL;
            sqlite3_exec(_sqlite3, "create table if not exists t_person(id integer primary key autoincrement,name text,age integer,gender boolean)", NULL, NULL, &errmsg);
            if (errmsg) {
                NSLog(@"创建表失败");
            } else {
                NSLog(@"创建表成功");
                sqlite3_close(_sqlite3);
            }
        } else {
            NSLog(@"数据库打开失败");
        }
    }
    #pragma mark 插入数据
    - (void)insertDataToDB {
        //在进行插入操作之前,要先进行打开数据库
        //sqlite3_exec可执行除了查询外的其他所有操作,增,删,改
        NSString *dbPath = [[self getDocumentsPath]stringByAppendingPathComponent:@"person.sqlite"];
        NSInteger openResult = sqlite3_open(dbPath.UTF8String, &_sqlite3);
        
        if (openResult == SQLITE_OK ) {
            for (NSInteger i = 0; i < 100; i ++) {
                NSString *insertSql = [[NSString alloc]initWithFormat:@"insert into t_person (name,age,gender) values ('%@','%ld','%d')",@"lvjiazhen",(long)(i + 2),0];
                char *errmsg = NULL;
                sqlite3_exec(_sqlite3, insertSql.UTF8String, NULL, NULL, &errmsg);
                if (errmsg) {
                    NSLog(@"第%ld条语句插入失败",(long)i);
                } else {
                    NSLog(@"第%ld条语句插入成功",(long)i);
                }
            }
            //关闭数据库
            sqlite3_close(_sqlite3);
        }
        
        
    }
    #pragma mark 查询数据
    - (void)readDataFromDB {
    //    sqlite3_prepare_v2() : 检查sql的合法性
    //    
    //    sqlite3_step() : 逐行获取查询结果,不断重复,直到最后一条记录
    //    
    //    sqlite3_coloum_xxx() : 获取对应类型的内容,iCol对应的就是SQL语句中字段的顺序,从0开始。根据实际查询字段的属性,使用sqlite3_column_xxx取得对应的内容即可。
    //    
    //    sqlite3_finalize() : 释放stmt
        NSString *dbPath = [[self getDocumentsPath]stringByAppendingPathComponent:@"person.sqlite"];
        NSInteger openResult = sqlite3_open(dbPath.UTF8String, &_sqlite3);
        if (openResult == SQLITE_OK) {
            NSMutableArray *mutArray = [NSMutableArray new];
            char *selectSql = "select name,age from t_person";
            sqlite3_stmt *stmt;
            int result = sqlite3_prepare_v2(_sqlite3, selectSql, -1, &stmt, NULL);
    
            if (result == SQLITE_OK) {
                while (sqlite3_step(stmt) == SQLITE_ROW) {
                    char *name = (char *)sqlite3_column_text(stmt, 0);//这里的序列号0或1对应的是selectSql里的列号,而不是字段在表里的列号
                    NSInteger age = sqlite3_column_int(stmt, 1);
                    PersonModel *person = [PersonModel new];
                    person.name = [NSString stringWithUTF8String:name];
                    person.height = age;
                    [mutArray addObject:person];
                }
                //释放结果集
                free(stmt);
                //关闭数据库
                sqlite3_close(_sqlite3);
            }
            
            NSLog(@"%@",mutArray);
        }
    }

    FMDB有三个最主要的类:
    FMDatabase、FMResultSet、FMDatabaseQueue
    FMDatabaseQueue:在不同的线程同时进行读写,这时候必须进行锁操作。

  • 相关阅读:
    CommonJs、AMD、CMD模块化规范
    在 repeater控件中有button控件,如何点击button按钮在后头产生方法
    DropdownList动态绑定数据库中的字段
    电脑操作的快捷键
    多条件查询
    asp.Net_图片上传的一个类库的源码
    日期转换_中文To数字
    VS2010断点设置技巧
    附加进程调试
    电脑常用的几个快捷键
  • 原文地址:https://www.cnblogs.com/i-am-lvjiazhen/p/6498383.html
Copyright © 2020-2023  润新知