蠅量級的JSON模型轉換庫(OC,Swift通用)

Github地址html

看到JSON模型轉換庫,相信你們都沒興趣了,由於各自項目裏確定早都有了。其實也不是我想重複造輪子,主要是這個庫其實早在2012年這個庫就有了,而那時既沒有小碼哥的MJExtension,也沒有牛逼閃閃的YYModel。那時的JSON模型轉換庫沒有幾個,並且都又大又難用,因此我當時本身寫了一個。這個庫在公司的項目中一直沿用至今,中間我陸續作了點擴展,也優化了一下性能,總體仍是很是穩定可用的。git

今天忽然想把它拿出來,給你們分享一下。由於這個庫雖然不是性能最強最全面的,可是它足夠的簡單,直白,只有兩個文件,300行代碼,可供初學者學習,也可用你們自行擴展知足本身的需求。github

特性

  • 極簡,沒有複雜的調用,只有300行代碼。
  • 市面上其餘庫支持的轉換,這個基本上都有了。
  • Swift下也可使用。
  • 性能優於MJExtention。

使用

和其餘庫基本上是差很少的。例子能夠參考main.m。json

Swift下使用

這個庫自己是用OC寫的,若是是純Swift項目就不建議用了。可是對於OC和Swift混編項目,尤爲是從OC轉過來的項目,仍是很好用的。由於OC下的轉換一般只要一行代碼,項目切換到Swift之後,若是用SwiftyJSON仍是挺不習慣的。用這個庫可讓OC和Swift裏的開發習慣保持基本一致。swift

Swift下的示例能夠參考SwiftJsonTest.swift。 具體有以下幾點要求:數組

  • 模型類須要繼承自NSObject,並用@objcMembers聲明。
  • 枚舉須要繼承自Int,並用@objc聲明。
  • Int等基礎數據類型屬性不能聲明爲可選,須要賦初始值。
  • 枚舉屬性不能聲明爲可選,須要賦初始值。
  • 數組屬性若是包含自定義類型的元素,映射的時候元素類名前要加上模塊名(App名或庫名)

代碼示例以下:性能優化

@objc enum OrderState: Int {
    case created = 1
    case completed = 2
    case canceled = 3
}

@objcMembers class OrderInfo: NSObject, JSONEx {
    var state: OrderState = .created
    var ID: String?
    var count: Int = 0
    var product: ProductInfo?
    
    static func customPropertyNameForKeys() -> [String : String] {
        return ["ID": "id"]
    }
}

@objcMembers class School: NSObject, JSONEx {
    var address: String?
    var students: [Student] = []
    
    static func arrayPropertyItemClasses() -> [String : String] {
        return ["students": "JSONEx.Student"]
    }
}
複製代碼

調用的時候一句就能夠了:bash

let obj: PhoneInfo = PhoneInfo(dictionary: dic)
複製代碼

和OC下的使用是基本一致的。多線程

實現

總體的實現都很是簡單直白,只有字典NSDictionary給對象屬性賦值的部分代碼稍微多一點,這個賦值是基於KVC作的,若是屬性有setter方法的話KVC的效率是不差的。對象的基類屬性的賦值是經過遞歸作的。 主要的方法以下:app

@implementation NSDictionary (NSDictionary_ObjectMapping)

- (void)setObject:(id)obj fromClass:(Class)cls
{
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &count); //獲取該類的全部屬性
    for (int i = 0; i < count; i++) {
        
        objc_property_t property = properties[i];
        const char* key = property_getName(property);
        NSString* strKey = [NSString stringWithCString:key encoding:NSUTF8StringEncoding];
        id value = [self objectForKey:strKey];//從字典裏按屬性名獲取value
        if (value == nil) {
            //對象屬性名和JSON關鍵詞key不一致的狀況,獲取屬性名對應的key
            NSString *jsonKey = [self getObjCustomPropertyKey:obj properyName:strKey];
            if (jsonKey != nil) {
                value = [self objectForKey:jsonKey];//再次從字典裏獲取value
            }
        }
        
        Class propertyCls = [self getPropertyClass:property];//獲取屬性的類
        if (propertyCls == nil || [self isFoundationClass:propertyCls]) {
            if(value != nil && value != (id)kCFNull) {
                if (propertyCls == [NSURL class] && [value isKindOfClass:[NSString class]]) {
                    [obj setValue:[NSURL URLWithString:value] forKey:strKey];
                } else if (propertyCls == [NSArray class] && [value isKindOfClass:[NSArray class]]) {
                    //數組屬性裏的元素是自定義類型
                    Class cls = [self getObjArrayPropertyClass:obj properyName:strKey];
                    if (cls) {
                        [obj setValue:[cls objectArrayWithArray:value] forKey:strKey];
                    } else {
                        [obj setValue:value forKey:strKey];
                    }
                } else {
                    [obj setValue:value forKey:strKey];//經過KVC給對象的屬性賦值
                }
            }
        } else {//自定義類型屬性
            if(value != nil && [value isKindOfClass:[NSDictionary class]]) {
                id subObj = [[propertyCls alloc] init];
                [value setObject:subObj fromAllClass:propertyCls];
                [obj setValue:subObj forKey:strKey];
            }
        }
    }
    free(properties);
}

@end
複製代碼

步驟以下:

  1. 經過class_copyPropertyList獲取類的屬性列表
  2. 從字典裏按屬性名獲取value
  3. 若是未獲取到,則查看該屬性名是否設置了對應的Key
  4. 若是設置了則再次從字典裏獲取value
  5. 獲取屬性的類型Class
  6. 判斷屬性是不是自定義的類型
  7. 若是是,那麼遞歸調用該方法給該屬性的屬性賦值
  8. 若是是基礎數據類型,則直接經過KVC給該屬性賦值
  9. 若是是數組,則查看是否指定了該數據元素的類型
  10. 指定了則調用JSON數組轉對象數組的方法

性能優化

主要優化了兩個地方,一是獲取屬性類型的地方,而是訪問擴展設置方法的地方。

- (Class)getPropertyClass:(objc_property_t)property
{
    unsigned int attrCount;
    objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
    for (unsigned int i = 0; i < attrCount; i++) {
        const char *name = attrs[i].name;
        const char *value = attrs[i].value;
        if (name[0] == 'T') {
            if (strlen(value) > 2 && value[0] == '@' && value[1] == '\"') {
                char *p = strrchr(value + 2, '\"');
                if (p) {
                    *p = '\0';
                    free(attrs);
                    return objc_getClass(value + 2);
                }
            }
            break;
        }
    }
    free(attrs);
    return nil;
}
複製代碼

這裏直接對char字符串操做,效率高不少,以前是先轉成NSString對象的。關於屬性的信息你們能夠查這裏Property Attribute Description Examples。 對象是T@開頭,後面接着的就是類型。

還有一個優化是關於擴展設置方法的:

@protocol JSONEx <NSObject>
@optional
+ (NSDictionary<NSString *, NSString *> *)arrayPropertyItemClasses;//數組屬性裏元素的類型
+ (NSDictionary<NSString *, NSString *> *)customPropertyNameForKeys;//屬性名和JSON關鍵詞的映射
@end
複製代碼

每次都讀取這兩個方法很耗時。我參考YYModel的作法把這兩個方法返回的設置一次性讀到一個全局的靜態字典裏,再來訪問這個靜態變量,速度就快了。代碼以下:

// 用靜態字典存取類的customPropertyNameForKeys方法的值,以免函數調用,提升訪問速度
static NSDictionary* customPropertyKeyForClass(Class cls) {
    if (!cls || ![cls respondsToSelector:@selector(customPropertyNameForKeys)]) return nil;
    static CFMutableDictionaryRef cache;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    NSDictionary *dic = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);
    if (!dic) {
        dic = [(id<JSONEx>)cls customPropertyNameForKeys];
        if (dic) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(dic));
            dispatch_semaphore_signal(lock);
        }
    }
    return dic;
}
複製代碼

這裏用GCD的信號量保證多線程狀況下的靜態字典的讀寫互斥。

最終的結果: 用YYModel裏的用例,循環解析10000遍,在iPhone 7模擬器上,耗時大幅優於MJExtention。

JSONEx: 640.83ms YYModel: 176.11ms MJExtension: 2196.75ms

相關文章
相關標籤/搜索