MJExtension源碼解讀(一)

內(nèi)容提要: MJExtension是一套字典和模型之間互相轉(zhuǎn)換的超輕量級框架。字典轉(zhuǎn)model是最典型的一個運用場景刻恭,我對MJExtension的理解是:

1.首先是對已經(jīng)建立的model通過運行時取出該model的屬性編入一個屬性數(shù)組;

2.然后是對該屬性數(shù)組進行遍歷扯夭,在該過程中鳍贾,取出相應(yīng)屬性值的字典的value值。

3.把取出來的value值賦值給model的匹配屬性交洗。

4.遍歷結(jié)束即賦值結(jié)束骑科,也就是字典轉(zhuǎn)model結(jié)束。

5.MJExtension做的比較好的地方是對數(shù)據(jù)的處理做到了極致构拳,和容錯判斷的處理咆爽。其中數(shù)據(jù)的處理包括對模型屬性值的類型的判斷和對字典每個value類型的判斷的處理梁棠。

本篇進行簡單的字典轉(zhuǎn)model的解讀,其他的轉(zhuǎn)換model方法會在下一篇給出解釋斗埂。
參看如下事例:

 // 1.定義一個字典
    NSDictionary *dict = @{
                           @"name" : @"Jack",
                           @"icon" : @"lufy.png",
                           @"age" : @"20",
                           @"height" : @1.55,
                           @"money" : @"100.9",
                           @"sex" : @(SexFemale),
                           @"gay" : @"1"
                       //  @"gay" : @"NO"
                       //  @"gay" : @"true"
                           };
                           
    // 2.將字典轉(zhuǎn)為MJUser模型
    MJUser *user = [MJUser mj_objectWithKeyValues:dict];
    
    // 3.打印MJUser模型的屬性
    MJExtensionLog(@"name=%@, icon=%@, age=%zd, height=%@, money=%@, sex=%d, gay=%d", user.name, user.icon, user.age, user.height, user.money, user.sex, user.gay);

事例是直接從demo中復(fù)制得到符糊,源碼解讀從mj_objectWithKeyValues:開始。
點擊該方法進入實現(xiàn):

+ (instancetype)mj_objectWithKeyValues:(id)keyValues
{
    return [self mj_objectWithKeyValues:keyValues context:nil];
}

在這里context參數(shù)傳nil,其中NSManagedObjectContext是:一個對于數(shù)據(jù)庫的封裝呛凶,只要能保存在數(shù)據(jù)庫中的內(nèi)容濒蒋,都可以保存在NSMangedObjectContext中。它的地址是通過NSPersistentStoreCoordinator定義的把兔,一般存放在應(yīng)用程序的Document目錄下。

+ (instancetype)mj_objectWithKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
    // 獲得JSON對象
    keyValues = [keyValues mj_JSONObject];
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], nil, [self class], @"keyValues參數(shù)不是一個字典");
    
    if ([self isSubclassOfClass:[NSManagedObject class]] && context) {
        NSString *entityName = [NSStringFromClass(self) componentsSeparatedByString:@"."].lastObject;
        return [[NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context] mj_setKeyValues:keyValues context:context];
    }
    return [[[self alloc] init] mj_setKeyValues:keyValues];
}

一.先獲得json對象

針對mj_JSONObject方法實現(xiàn)

- (id)mj_JSONObject
{
    if ([self isKindOfClass:[NSString class]]) {
        return [NSJSONSerialization JSONObjectWithData:[((NSString *)self) dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];
    } else if ([self isKindOfClass:[NSData class]]) {
        return [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil];
    }
    
    return self.mj_keyValues;
}

判斷該對象是否是NSString對象或NSData對象瓮顽,若是其中一個县好,則中NSJSONSerialization+ (nullable id)JSONObjectWithData: options: error:方法實現(xiàn)轉(zhuǎn)為json對象;因為本演示demo是一個字典暖混,故會執(zhí)行mj_keyValues方法缕贡。該方法實現(xiàn)如下:

#pragma mark - 模型 -> 字典
- (NSMutableDictionary *)mj_keyValues
{
    return [self mj_keyValuesWithKeys:nil ignoredKeys:nil];
}
- (NSMutableDictionary *)mj_keyValuesWithKeys:(NSArray *)keys ignoredKeys:(NSArray *)ignoredKeys
{
    // 如果自己不是模型類, 那就返回自己
    MJExtensionAssertError(![MJFoundation isClassFromFoundation:[self class]], (NSMutableDictionary *)self, [self class], @"不是自定義的模型類")
     
     id keyValues = [NSMutableDictionary dictionary];
    ...//此處省略了模型轉(zhuǎn)字典的具體實現(xiàn)。
    return keyValues;
}

其中MJExtensionAssertError是一個斷言拣播,具體定義為:

#define MJExtensionAssertError(condition, returnValue, clazz, msg) \
[clazz setMj_error:nil]; \
if ((condition) == NO) { \
    MJExtensionBuildError(clazz, msg); \
    return returnValue;\
} 

二.斷言判斷

MJExtensionAssertError斷言判斷該keyValues參數(shù)是否是一個字典晾咪。如果不是一個字典,則返回nil贮配。

三.判斷上下文

在這里該類不是NSManagedObject對象沒并且NSManagedObjectContext參數(shù)傳的也是nil,所以不會進入此判斷谍倦。

四.創(chuàng)建模型

mj_setKeyValues:實現(xiàn)如下:

#pragma mark - 字典 -> 模型
- (instancetype)mj_setKeyValues:(id)keyValues
{
    return [self mj_setKeyValues:keyValues context:nil];
}

接下來重點來了,看看字典是怎么轉(zhuǎn)為模型的:

/**
 核心代碼:
 */
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
    // 獲得JSON對象
    keyValues = [keyValues mj_JSONObject];
    //判斷是否是字典泪勒,如果不是一個字典的話昼蛀,直接返回self。
    MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個字典");
    
    ...
    
    //通過封裝的方法回調(diào)一個通過運行時編寫的圆存,用于返回屬性列表的方法叼旋。
    [clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
        @try {
            // 0.檢測是否被忽略
            if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
            if ([ignoredPropertyNames containsObject:property.name]) return;
            
            // 1.取出屬性值
            id value;
            NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
            for (NSArray *propertyKeys in propertyKeyses) {
                value = keyValues;
                for (MJPropertyKey *propertyKey in propertyKeys) {
                    value = [propertyKey valueInObject:value];
                }
                if (value) break;
            }
            
            // 值的過濾
            ...
            
            // 如果沒有值,就直接返回
            if (!value || value == [NSNull null]) return;
            
            // 2.復(fù)雜處理
            ...
            
            // 3.賦值
            [property setValue:value forObject:self];
        } @catch (NSException *exception) {
            MJExtensionBuildError([self class], exception.reason);
            MJExtensionLog(@"%@", exception);
        }
    }];
    
    // 轉(zhuǎn)換完畢
    ...
    return self;
}

核心代碼的思路很清晰:
1.先獲模型取所有的屬性沦辙,mj_enumerateProperties:是一個枚舉block,在里邊執(zhí)行具體給model賦值的工作夫植。
2.當(dāng)具體執(zhí)行每一個屬性的時候,先檢測是否被忽略油讯,若是被忽略详民,則直接return跳出該循環(huán),進行寫一個屬性賦值撞羽。
3.取出屬性值阐斜,如果沒有值,直接返回诀紊,不進行下列步驟谒出。
4.復(fù)雜處理:處理屬性是什么類型隅俘,對應(yīng)的value的處理。
5進行賦值:針對復(fù)雜處理過的value對model進行賦值笤喳,經(jīng)過這一步为居,該model的賦值過的屬性已經(jīng)是有值了。
6.轉(zhuǎn)換完畢杀狡,返回model自身蒙畴。

總結(jié):字典轉(zhuǎn)model是最典型的一個運用場景,我對MJExtension的理解是:

1.首先是對已經(jīng)建立的model通過運行時取出該model的屬性編入一個屬性數(shù)組呜象;
2.然后是對該屬性數(shù)組進行遍歷膳凝,在該過程中,取出相應(yīng)屬性值的字典的value值恭陡。
3.把取出來的value值賦值給model的屬性蹬音。
4.遍歷結(jié)束即賦值結(jié)束,也就是字典轉(zhuǎn)model結(jié)束休玩。
5.MJExtension做的比較好的地方是對數(shù)據(jù)的處理做到了極致著淆,和容錯判斷的處理。其中數(shù)據(jù)的處理包括對模型屬性值的類型的判斷和對字典每個value類型的判斷的處理拴疤。

有關(guān)思考:
1.利用MJExtension能否對有只讀屬性的model進行賦值永部?
答:可以,我作了一下測試呐矾,我對例子中的模型MJUser的name屬性添加了一個readonly修飾苔埋,變成了只讀屬性。因為是只讀屬性蜒犯,明文操作像

    MJUser *user = [[MJUser alloc] init];
    user.name = @"Jack";//會提示Assignment to readonly property

意思是只讀屬性讲坎,無法在外部進行賦值。
還是文章開頭的例子打印結(jié)果如下圖:

圖一.png

由打印結(jié)果看出:只讀屬性是可以用runtime進行賦值的愧薛。

MJExtension源碼地址:https://github.com/CoderMJLee/MJExtension

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晨炕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子毫炉,更是在濱河造成了極大的恐慌瓮栗,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞄勾,死亡現(xiàn)場離奇詭異费奸,居然都是意外死亡,警方通過查閱死者的電腦和手機进陡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門愿阐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人趾疚,你說我怎么就攤上這事缨历∫栽蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵辛孵,是天一觀的道長丛肮。 經(jīng)常有香客問我,道長魄缚,這世上最難降的妖魔是什么宝与? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮冶匹,結(jié)果婚禮上习劫,老公的妹妹穿的比我還像新娘。我一直安慰自己嚼隘,他們只是感情好榜聂,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嗓蘑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪匿乃。 梳的紋絲不亂的頭發(fā)上桩皿,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音幢炸,去河邊找鬼泄隔。 笑死,一個胖子當(dāng)著我的面吹牛宛徊,可吹牛的內(nèi)容都是我干的佛嬉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼闸天,長吁一口氣:“原來是場噩夢啊……” “哼暖呕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苞氮,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤湾揽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后笼吟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體库物,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年贷帮,在試婚紗的時候發(fā)現(xiàn)自己被綠了戚揭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡撵枢,死狀恐怖民晒,靈堂內(nèi)的尸體忽然破棺而出精居,到底是詐尸還是另有隱情,我是刑警寧澤镀虐,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布箱蟆,位于F島的核電站,受9級特大地震影響刮便,放射性物質(zhì)發(fā)生泄漏空猜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一恨旱、第九天 我趴在偏房一處隱蔽的房頂上張望辈毯。 院中可真熱鬧,春花似錦搜贤、人聲如沸谆沃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唁影。三九已至,卻和暖如春掂名,著一層夾襖步出監(jiān)牢的瞬間据沈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工饺蔑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锌介,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓猾警,卻偏偏與公主長得像孔祸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子发皿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355