天天看點

iOS 序列化與反序列化(runtime) 02

//聯系人:石虎  QQ: 1224614774昵稱:嗡嘛呢叭咪哄

一、runtime: iOS序列化與反序列化利器

1.1 總體思路

觀察上面的

initWithCoder

代碼我們可以發現,序列化與反序列化中最重要的環節是周遊類的變量,保證不能遺漏。

這裡需要特别注意的是:

編解碼的範圍不能僅僅是自身類的變量,還應當把除NSObject類外的所有層級父類的屬性變量也進行編解碼!

由此可見,這幾乎是個純體力活。而runtime在周遊變量這件事情上能為我們提供什麼幫助呢?我們可以通過runtime在運作時擷取自身類的所有變量進行編解碼;然後對父類進行遞歸,擷取除NSObject外每個層級父類的屬性(非私有變量),進行編解碼。

1.2 使用runtime擷取變量以及屬性

runtime中擷取某類的所有變量(屬性變量以及執行個體變量)API:

擷取某類的所有屬性變量API:

runtime的所有開放API都放在

objc/runtime.h

裡面。上面的一些資料類型有些同學可能沒見過,這裡我們先簡單地介紹一下,更詳細的介紹請自行查閱其他資料,強烈建議打開

Ivar是runtime對于變量的定義,本質是一個結構體:

struct objc_ivar {
    char *ivar_name;                                   
    char *ivar_type;                                    
    int ivar_offset;
#ifdef __LP64__
    int space;
#endif
} 
typedef struct objc_ivar *Ivar;                
  • ivar_name:變量名,對于一個給定的Ivar,可以通過

    const char *ivar_getName(Ivar v)

    函數獲得

    char *

    類型的變量名;
  • ivar_type: 變量類型,在runtime中變量類型用字元串表示,例如用@表示id類型,用i表示int類型...。這不在本文讨論之列。類似地,可以通過

    const char *ivar_getTypeEncoding(Ivar v)

    函數獲得變量類型;
  • ivar_offset: 基位址偏移位元組數,可以不用理會

    擷取所有變量的代碼一般長這樣子:

    unsigned int numIvars; //成員變量個數
      Ivar *vars = class_copyIvarList(NSClassFromString(@"UIView"), &numIvars);
      NSString *key=nil;
      for(int i = ; i < numIvars; i++) {
          Ivar thisIvar = vars[i];
          key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];  //擷取成員變量的名字
          NSLog(@"variable name :%@", key);
          key = [NSString stringWithUTF8String:ivar_getTypeEncoding(thisIvar)]; //擷取成員變量的資料類型
          NSLog(@"variable type :%@", key);
      }
      free(vars);//記得釋放掉
               
    objc_property_t是runtime對于屬性變量的定義,本質上也是一個結構體(事實上OC是對C的封裝,大多數類型的本質都是C結構體)。在

    runtime.h

    頭檔案中隻有

    typedef struct objc_property *objc_property_t

    ,并沒有更詳細的結構體介紹。雖然runtime的源碼是開源的,但這裡并不打算深入介紹,這并不影響我們今天的主題。與Ivar的應用同理,擷取類的屬性變量的代碼一般長這樣子:
    unsigned int outCount, i;   
      objc_property_t *properties = class_copyPropertyList([self class], &outCount);   
      for (i = ; i < outCount; i++) {   
          objc_property_t property = properties[i];   
          NSString *propertyName = [[[NSString alloc] initWithCString:property_getName(property)] ;   
          NSLog(@"property name:%@", propertyName); 
      }   
      free(properties);
               

    1.3 用runtime實作序列化與反序列化

    有了前面兩節的鋪墊,到這裡自然就水到渠成了。我們可以在

    initWithCoder:

    以及

    encoderWithCoder:

    中周遊類的所有變量,取得變量名作為KEY值,最後使用KVC強制取得或者指派給對象。于是我們可以得到如下的自動序列化與發序列化代碼,關鍵部分有注釋:
    @implementation Person
    //解碼
    - (id)initWithCoder:(NSCoder *)coder
    {
      unsigned int iVarCount = ;
      Ivar *iVarList = class_copyIvarList([self class], &iVarCount);//取得變量清單,[self class]表示對自身類進行操作
      for (int i = ; i < iVarCount; i++) {
          Ivar var = *(iVarList + i);
          const char * varName = ivar_getName(var);//取得變量名字,将作為key
          NSString *key = [NSString stringWithUTF8String:varName];
          //decode
          id  value = [coder decodeObjectForKey:key];//解碼
          if (value) {
              [self setValue:value forKey:key];//使用KVC強制寫入到對象中
          }
      }
      free(iVarList);//記得釋放記憶體
      return self;
    }
        //編碼
        - (void)encodeWithCoder:(NSCoder *)coder
            {
            unsigned int varCount = ;
            Ivar *ivarList = class_copyIvarList([self class], &varCount);
            for (int i = ; i < varCount; i++) {
                Ivar var = *(ivarList + i);
                const char *varName = ivar_getName(var);
                NSString *key = [NSString stringWithUTF8String:varName];
                id varValue = [self valueForKey:key];//使用KVC擷取key對應的變量值
                if (varValue) {
                    [coder encodeObject:varValue forKey:key];
                }
          }
          free(ivarList);
        }
               

    1.4 優化

    上面代碼有個缺陷,在擷取變量時都是指定目前類,也就是

    [self class]

    。當你的Model對象并不是直接繼承自NSObject時容易遺漏掉父類的屬性。請牢記3.1節我們提到的:
    編解碼的範圍不能僅僅是自身類的變量,還應當把除NSObject類外的所有層級父類的屬性變量也進行編解碼!

是以在上面代碼的基礎上我們我們需要注意一下細節,設一個指針,先指向本身類,處理完指向SuperClass,處理完再指向SuperClass的SuperClass...。代碼如下(這裡僅以

encodeWithCoder:

為例,畢竟

initWithCoder:

同理):

- (void)encodeWithCoder:(NSCoder *)coder
{ 
    Class cls = [self class];
    while (cls != [NSObject class]) {//對NSObject的變量不做處理
        unsigned int iVarCount = ;
        Ivar *ivarList = class_copyIvarList([cls class], &iVarCount);/*變量清單,含屬性以及私有變量*/  
        for (int i = ; i < iVarCount; i++) { 
            const char *varName = ivar_getName(*(ivarList + i)); 
            NSString *key = [NSString stringWithUTF8String:varName];    
            /*valueForKey隻能擷取本類所有變量以及所有層級父類的屬性,不包含任何父類的私有變量(會崩潰)*/  
            id varValue = [self valueForKey:key];   
            if (varValue) { 
                [coder encodeObject:varValue forKey:key];   
            }   
        }   
        free(ivarList); 
        cls = class_getSuperclass(cls); //指針指向目前類的父類
    }   
}
           

這樣真的結束了嗎?不是的。當你的跑上面的代碼時程式有可能會crash掉,crash的地方在

[self valueForKey:key]

這一句上。原來是這裡的KVC無法擷取到父類的私有變量(即執行個體變量)。是以,在處理到父類時不能簡單粗暴地使用

class_copyIvarList

,而隻能取父類的屬性變量。這時候3.2節部分的

class_copyPropertyList

就派上用場了。在處理父類時用後者代替前者。

2016.0804補充

在最近的iOS中列印propertyList會發現有

superClass

description

debugDescription

hash

等四個屬性。對這幾個屬性進行encode操作會導緻crash。是以在encode前需要屏蔽掉這些key

于是最終的代碼(額~其實還不算最終):

- (id)initWithCoder:(NSCoder *)coder    
{   
    NSLog(@"%s",__func__);  
    Class cls = [self class];   
    while (cls != [NSObject class]) {   
        /*判斷是自身類還是父類*/    
        BOOL bIsSelfClass = (cls == [self class]);  
        unsigned int iVarCount = ; 
        unsigned int propVarCount = ;  
        unsigned int sharedVarCount = ;    
        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 = ; i < sharedVarCount; i++) {  
            const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); 
            NSString *key = [NSString stringWithUTF8String:varName];   
            id varValue = [coder decodeObjectForKey:key]; 
            NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"];   
            if (varValue && [filters containsObject:key] == NO) { 
                [self setValue:varValue forKey:key];    
            }   
        }   
        free(ivarList); 
        free(propList); 
        cls = class_getSuperclass(cls); 
    }   
    return self;    
}   

- (void)encodeWithCoder:(NSCoder *)coder    
{   
    NSLog(@"%s",__func__);  
    Class cls = [self class];   
    while (cls != [NSObject class]) {   
        /*判斷是自身類還是父類*/    
        BOOL bIsSelfClass = (cls == [self class]);  
        unsigned int iVarCount = ; 
        unsigned int propVarCount = ;  
        unsigned int sharedVarCount = ;    
        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 = ; 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) { 
                [coder encodeObject:varValue forKey:key];   
            }   
        }   
        free(ivarList); 
        free(propList); 
        cls = class_getSuperclass(cls); 
    }   
}
           

1.5 最終的封裝

在邏輯上,上面的代碼應該是目前為止比較完美的自動序列化與反序列解決方案了。即使某個類的繼承深度極其深,變量極其多,序列化的代碼也就以上這些。但是我們回到文章第二節提出的幾點場景假設,其中有一點提到:

若你的工程中有很多像Person的自定義類需要做序列化操作呢?

如果是在以上場景下,每個Model類都需要寫一次上面的代碼。這在一定程度上也造成備援了。同時,你也會覺得這篇文章的标題就是瞎扯淡,根本就不是一行代碼的事。上面的代碼備援,我這種對代碼有很強潔癖的程式旺是萬萬接受不了的。那就再封裝一層!這裡我采用宏的方式将上述代碼濃縮成一行,放到一個叫WZLSerializeKit.h的頭檔案中:

#define WZLSERIALIZE_CODER_DECODER()     \
\
- (id)initWithCoder:(NSCoder *)coder    \
{   \
    NSLog(@"%s",__func__);  \
    Class cls = [self class];   \
    while (cls != [NSObject class]) {   \
        /*判斷是自身類還是父類*/    \
        BOOL bIsSelfClass = (cls == [self class]);  \
        unsigned int iVarCount = ; \
        unsigned int propVarCount = ;  \
        unsigned int sharedVarCount = ;    \
        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 = ; i < sharedVarCount; i++) {  \
            const char *varName = bIsSelfClass ? ivar_getName(*(ivarList + i)) : property_getName(*(propList + i)); \
            NSString *key = [NSString stringWithUTF8String:varName];   \
            id varValue = [coder decodeObjectForKey:key];   \
            NSArray *filters = @[@"superclass", @"description", @"debugDescription", @"hash"]; \
            if (varValue && [filters containsObject:key] == NO) { \
                [self setValue:varValue forKey:key];    \
            }   \
        }   \
        free(ivarList); \
        free(propList); \
        cls = class_getSuperclass(cls); \
    }   \
    return self;    \
}   \
\
- (void)encodeWithCoder:(NSCoder *)coder    \
{   \
    NSLog(@"%s",__func__);  \
    Class cls = [self class];   \
    while (cls != [NSObject class]) {   \
        /*判斷是自身類還是父類*/    \
        BOOL bIsSelfClass = (cls == [self class]);  \
        unsigned int iVarCount = ; \
        unsigned int propVarCount = ;  \
        unsigned int sharedVarCount = ;    \
        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 = ; 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) { \
                [coder encodeObject:varValue forKey:key];   \
            }   \
        }   \
        free(ivarList); \
        free(propList); \
        cls = class_getSuperclass(cls); \
    }   \
}
           

之後需要序列化的地方隻要兩步:

1、import "WZLSerializeKit.h"

2、調用

WZLSERIALIZE_CODER_DECODER();

即可。兩個字:清爽。

此外,

copyWithZone

中同樣可以用相同的原理對變量進行自動化copy。同樣地,我們也可以用一個宏封裝掉

copyWithZone

方法。這裡就不再贅述。

值得一提的是,以上代碼我已經放到我的Github中,并且提供了CocoaPods支援。使用的時候隻需要pod `WZLSerializeKit`。點 此處 跳轉到我的Github.

謝謝!!!