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:在不同的線程同時進行讀寫,這時候必須進行鎖操作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM