Runtime在工作中的運(yùn)用

這篇文章是筆者結(jié)合一些參考文章和當(dāng)初學(xué)習(xí)Runtime的心得而寫的一篇總結(jié)肚豺,主要講解Runtime在工作中的運(yùn)用,沒有涉及到太底層的知識(shí)界拦,極盡詳略吸申,適合初中級(jí)學(xué)者,水平有限享甸,有錯(cuò)誤的地方截碴,還請大佬在評論中指出,一起快樂學(xué)習(xí)蛉威。持續(xù)更新中日丹。。蚯嫌。

1.Runtime簡介

  • Runtime 簡稱運(yùn)行時(shí)哲虾,是一套C語言的API(引入 )丙躏。OC 就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制束凑,其中最主要的是 消息機(jī)制晒旅。

  • 對于C語言,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)汪诉。

  • 對于OC废恋,函數(shù)的調(diào)用稱為消息發(fā)送,屬于動(dòng)態(tài)調(diào)用過程扒寄。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)鱼鼓,只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對應(yīng)的函數(shù)來調(diào)用。

2.Runtime消息機(jī)制

消息機(jī)制原理:對象根據(jù)方法編號(hào)SEL去映射表查找對應(yīng)的方法實(shí)現(xiàn)旗们。

驗(yàn)證

1.在main.m中創(chuàng)建一個(gè)對象蚓哩;

id object = [NSObject alloc];
object = [object init];

2.終端切換到該目錄下,執(zhí)行命令clang -rewrite-objc main.m,編譯后會(huì)生成一個(gè)main.cpp(C++文件);

3.在.cpp文件中搜索autoreleasepool喂很,可以找到上述對象創(chuàng)建的底層代碼懂算;

id object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
object = ((id (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("init"));

可以看出調(diào)用方法本質(zhì)就是發(fā)消息[[NSObject alloc]init]語句發(fā)了兩次消息嗡髓,第一次發(fā)了alloc 消息,第二次發(fā)送init 消息。

4.我們自己來嘗試實(shí)現(xiàn)赃份,首先導(dǎo)入頭文件 #import,然后讓消息機(jī)制方法有提示(【build setting -> 搜索msg -> objc_msgSend(YES --> NO)】)奢米。

id object = objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"));
object = objc_msgSend(object, sel_registerName("init"));
/**
 objc_getClass(const char *name) 獲取當(dāng)前類
 sel_registerName(const char *str) 注冊個(gè)方法編號(hào)
 objc_msgSend(id self:誰發(fā)送消息, SEL op:發(fā)送什么消息, ...:參數(shù))
 */

換個(gè)寫法:

id objc = objc_msgSend([NSObject class], @selector(alloc));
objc = objc_msgSend(objc, @selector(init));

參數(shù)處理:

objc_msgSend(p, sel_registerName("height:"), 180);

注:

objc_msgSend:這是個(gè)最基本的用于發(fā)送消息的函數(shù)抓韩。

其實(shí)編譯器會(huì)根據(jù)情況在objc_msgSendobjc_msgSend_fpret 鬓长,objc_msgSend_stret谒拴,objc_msgSendSuper, 或 objc_msgSendSuper_stret 五個(gè)方法中選擇一個(gè)來調(diào)用涉波。

如果消息是傳遞給超類英上,那么會(huì)調(diào)用名字帶有 Super 的函數(shù);
如果消息返回值是浮點(diǎn)數(shù)啤覆,那么會(huì)調(diào)用名字帶有fpret 的函數(shù)苍日;
如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時(shí),那么會(huì)調(diào)用名字帶有stret的函數(shù)窗声。

3.Runtime方法調(diào)用流程

  • 對象方法:(保存到類對象的方法列表)
  • 類方法:(保存到元類(Meta Class)中方法列表)

1.消息傳遞:
一個(gè)對象的方法像這樣[obj foo]相恃,編譯器轉(zhuǎn)成消息發(fā)送objc_msgSend(obj, foo)Runtime時(shí)執(zhí)行的流程是什么樣的吶嫌佑?

1.首先豆茫,通過objisa指針找到它的 class ;
2.注冊方法編號(hào)SEL,可以快速查找侨歉;
3.根據(jù)方法編號(hào),在 classmethod listfoo ;
3.如果 class 中沒到 foo揩魂,繼續(xù)往它的 superclass 中找 ;
4.一旦找到 foo 這個(gè)函數(shù)幽邓,就去執(zhí)行它的實(shí)現(xiàn)IMP

2.Runtime的三次轉(zhuǎn)發(fā)流程

image

動(dòng)態(tài)方法解析

Objective-C運(yùn)行時(shí)會(huì)調(diào)用 +resolveInstanceMethod:或者 +resolveClassMethod:火脉,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)牵舵。如果你添加了函數(shù),那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程(動(dòng)態(tài)添加方法)倦挂。如果未實(shí)現(xiàn)方法畸颅,運(yùn)行時(shí)就會(huì)移到下一步:forwardingTargetForSelector

備用接收者

如果目標(biāo)對象實(shí)現(xiàn)了-forwardingTargetForSelector:方援,Runtime 這時(shí)就會(huì)調(diào)用這個(gè)方法没炒,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對象的機(jī)會(huì)。如果還不能處理未知消息犯戏,就會(huì)進(jìn)入完整消息轉(zhuǎn)發(fā)階段送火。

完整消息轉(zhuǎn)發(fā)

Runtime系統(tǒng)會(huì)向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對象先匪。為接下來的完整的消息轉(zhuǎn)發(fā)生成一個(gè) NSMethodSignature對象种吸。NSMethodSignature 對象會(huì)被包裝成 NSInvocation 對象,forwardInvocation: 方法里就可以對 NSInvocation 進(jìn)行處理了呀非。如果未實(shí)現(xiàn)坚俗,Runtime則會(huì)發(fā)出 -doesNotRecognizeSelector: 消息,程序這時(shí)也就掛掉了岸裙。

4.Runtime動(dòng)態(tài)添加方法

使用場景:如果一個(gè)類方法非常多猖败,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表降允,可以使用動(dòng)態(tài)給某個(gè)類辙浑,添加方法解決。

經(jīng)典面試題:有沒有使用performSelector拟糕,其實(shí)主要想問你有沒有動(dòng)態(tài)添加過方法。

方法介紹

// 參數(shù)1:給哪個(gè)類添加方法
// 參數(shù)2:添加方法的方法編號(hào)SEL
// 參數(shù)3:添加方法的函數(shù)實(shí)現(xiàn)IMP(函數(shù)地址)
// 參數(shù)4:函數(shù)的類型倦踢,(返回值+參數(shù)類型)
class_addMethod(Class cls, SEL name, IMP imp, const char * types)

1.class_addMethod會(huì)添加一個(gè)覆蓋父類的實(shí)現(xiàn)送滞,但不會(huì)取代原有類的實(shí)現(xiàn)。

2.函數(shù)的類型官方文檔

方法示例

假如Person對象調(diào)用eat方法辱挥,而該方法并沒有實(shí)現(xiàn)犁嗅,則會(huì)報(bào)錯(cuò)。我們可以利用RuntimePerson類中動(dòng)態(tài)添加eat方法晤碘,來實(shí)現(xiàn)該方法的調(diào)用褂微。

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *p = objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"));
    p = objc_msgSend(p, sel_registerName("init"));

    [p performSelector:@selector(eat)];
}

@end
@implementation Person

/**
 void的前面沒有+功蜓、-號(hào),因?yàn)橹皇荂的代碼;
 必須有兩個(gè)指定參數(shù)(id self,SEL _cmd)
 */
void eat(id self, SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 當(dāng)一個(gè)對象調(diào)用未實(shí)現(xiàn)的方法宠蚂,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對應(yīng)的方法列表傳過來.
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        //函數(shù)的類型式撼,(返回值+參數(shù)類型) v:void @:對象->self :表示SEL->_cmd
        class_addMethod(self, sel, (IMP)eat, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}

@end

5.Runtime方法交換(Method Swizzling)

使用場景:當(dāng)?shù)谌娇蚣芑蛘呦到y(tǒng)原生方法功能不能滿足我們的時(shí)候,我們可以在保持系統(tǒng)原有方法功能的基礎(chǔ)上求厕,添加額外的功能著隆。

方法介紹

// 交換方法地址,交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(Method m1, Method m2)

方法封裝:為了后續(xù)調(diào)用方便,我們可以將Method Swizzling功能封裝為類方法呀癣,作為NSObject的類別美浦。

@interface NSObject (Swizzling)

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector
                         bySwizzledSelector:(SEL)swizzledSelector;

@end
#import "NSObject+Swizzling.h"
#import <objc/message.h>

@implementation NSObject (Swizzling)

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector
{
    Class class = [self class];
    //原有方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    //替換原有方法的新方法
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    //先嘗試給源SEL添加IMP,這里是為了避免源SEL沒有實(shí)現(xiàn)IMP的情況
    BOOL didAddMethod =
    class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {//添加成功:說明源SEL沒有實(shí)現(xiàn)IMP项栏,將源SEL的IMP替換到交換SEL的IMP
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {//添加失斊直妗:說明源SEL已經(jīng)有IMP,直接將兩個(gè)SEL的IMP交換即可
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
@end

方法示例:例如我們想要替換ViewController生命周期方法沼沈,可以這樣做流酬。

#import "UIViewController+Swizzling.h"
#import "NSObject+Swizzling.h"

@implementation UIViewController (Swizzling)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodSwizzlingWithOriginalSelector:@selector(viewWillAppear:) bySwizzledSelector:@selector(mj_viewWillAppear:)];
    });
}

- (void)mj_viewWillAppear:(BOOL)animated{
    [self mj_viewWillAppear:animated];
    
    NSLog(@"被調(diào)用了");
}
@end

1.swizzling建議在+load中完成。+load+initialize 是Objective-C runtime會(huì)自動(dòng)調(diào)用兩個(gè)類方法庆冕。+load 是在一個(gè)類被初始加載時(shí)調(diào)用康吵,一定會(huì)被調(diào)用;+initialize 是在應(yīng)用第一次調(diào)用該類的類方法或?qū)嵗椒ㄇ罢{(diào)用访递,相當(dāng)于懶加載方式晦嵌,可能不被調(diào)用。此外 +load 方法還有一個(gè)非常重要的特性拷姿,那就是子類惭载、父類和分類中的 +load 方法的實(shí)現(xiàn)是被區(qū)別對待的。換句話說在 Objective-C runtime 自動(dòng)調(diào)用 +load 方法時(shí)响巢,分類中的 +load 方法并不會(huì)對主類中的 +load 方法造成覆蓋描滔。

2.swizzling應(yīng)該只在dispatch_once 中完成,由于swizzling 改變了全局的狀態(tài),所以我們需要確保在任何情況下(多線程環(huán)境踪古,或者被其他人手動(dòng)再次調(diào)用+load方法)只交換一次含长,防止再次調(diào)用又將方法交換回來。+load方法本身即為線程安全伏穆,為什么仍需添加dispatch_once拘泞,其原因就在于+load方法本身無法保證其中代碼只被執(zhí)行一次。

6.Runtime動(dòng)態(tài)添加屬性

場景:分類是不能自定義屬性和變量的枕扫,這時(shí)候可以使用runtime動(dòng)態(tài)添加屬性方法陪腌;

原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間诗鸭。

方法

/** 關(guān)聯(lián)對象染簇、set方法
id object:給哪個(gè)對象添加關(guān)聯(lián),給哪個(gè)對象設(shè)置屬性
const void *key:關(guān)聯(lián)的key强岸,要求唯一锻弓,建議用char 可以節(jié)省字節(jié)
id value:關(guān)聯(lián)的value,給屬性設(shè)置的值
objc_AssociationPolicy policy:內(nèi)存管理的策略
*/
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// 獲取關(guān)聯(lián)的對象请唱、get方法
id objc_getAssociatedObject(id object, const void *key)
// 移除關(guān)聯(lián)的對象
void objc_removeAssociatedObjects(id object)

內(nèi)存策略對應(yīng)的屬性修飾表:

內(nèi)存策略 屬性修飾 描述
OBJC_ASSOCIATION_ASSIGN @property (assign) 或 @property (unsafe_unretained) 指定一個(gè)關(guān)聯(lián)對象的弱引用弥咪。
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong) @property (nonatomic, strong) 指定一個(gè)關(guān)聯(lián)對象的強(qiáng)引用,不能被原子化使用十绑。
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy) 指定一個(gè)關(guān)聯(lián)對象的copy引用聚至,不能被原子化使用。
OBJC_ASSOCIATION_RETAIN @property (atomic, strong) 指定一個(gè)關(guān)聯(lián)對象的強(qiáng)引用本橙,能被原子化使用扳躬。
OBJC_ASSOCIATION_COPY @property (atomic, copy) 指定一個(gè)關(guān)聯(lián)對象的copy引用,能被原子化使用甚亭。

示例:實(shí)現(xiàn)一個(gè)UIViewCategory添加自定義屬性defaultColor贷币。

@interface UIView (Color)

@property (nonatomic, strong) UIColor *defaultColor;

@end
  
@implementation UIView (Color)

static char kDefaultColorKey;
- (void)setDefaultColor:(UIColor *)defaultColor
{
    objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)defaultColor {
    return objc_getAssociatedObject(self, &kDefaultColorKey);
}

@end

7.NSCoding自動(dòng)歸檔解檔

場景:如果一個(gè)模型有許多個(gè)屬性,實(shí)現(xiàn)自定義模型數(shù)據(jù)持久化時(shí)亏狰,需要對每個(gè)屬性都實(shí)現(xiàn)一遍encodeObjectdecodeObjectForKey方法役纹,比較麻煩。我們可以使用Runtime來解決暇唾。

原理:用runtime提供的函數(shù)遍歷Model自身所有屬性促脉,并對屬性進(jìn)行encodedecode操作。

方法實(shí)現(xiàn)

#import "MJMusicModel.h"
#import <objc/runtime.h>

@implementation MJMusicModel

// 設(shè)置不需要?dú)w解檔的屬性
- (NSArray *)ignoredNames {
    return @[@"_musicUrl"];
}

// 歸檔調(diào)用方法
- (void)encodeWithCoder:(NSCoder *)encoder
{
    unsigned int count = 0;
    // 獲得這個(gè)類的所有成員變量
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        // 取出i位置對應(yīng)的成員變量
        Ivar ivar = ivars[i];
        // 獲得成員變量的名字
        const char *name = ivar_getName(ivar);
        // 將每個(gè)成員變量名轉(zhuǎn)換為NSString對象類型
        NSString *key = [NSString stringWithUTF8String:name];
        // 忽略不需要?dú)w檔的屬性
        if ([[self ignoredNames] containsObject:key]) {
            continue;
        }
        // 歸檔
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    // 注意釋放內(nèi)存策州!
    free(ivars);
}

// 解檔方法
- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([self class], &count);
        for (int i = 0; i<count; i++) {
            // 取出i位置對應(yīng)的成員變量
            Ivar ivar = ivars[i];
            // 獲得成員變量的名字
            const char *name = ivar_getName(ivar);
            // 將每個(gè)成員變量名轉(zhuǎn)換為NSString對象類型
            NSString *key = [NSString stringWithUTF8String:name];
            // 忽略不需要解檔的屬性
            if ([[self ignoredNames] containsObject:key]) {
                continue;
            }
            // 解檔
            id value = [decoder decodeObjectForKey:key];
            // 設(shè)置到成員變量身上
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}

@end

:我們可以將歸解檔兩個(gè)方法封裝為宏瘸味,在需要的地方一句宏搞定;也可以寫到NSObject一個(gè)分類中够挂,方便使用旁仿。

8.Runtime字典轉(zhuǎn)模型

原理:利用Runtime,遍歷模型中所有屬性孽糖,根據(jù)模型的屬性名枯冈,去字典中查找key,取出對應(yīng)的值办悟,給模型的屬性賦值霜幼。

步驟:提供一個(gè)NSObject分類,專門字典轉(zhuǎn)模型誉尖,以后所有模型都可以通過這個(gè)分類實(shí)現(xiàn)字典轉(zhuǎn)模型。

接下來分別介紹一下三種情況所實(shí)現(xiàn)的代碼:

1.簡單的字典轉(zhuǎn)模型

注意:模型屬性數(shù)量大于字典的鍵值對時(shí)铸题,由于屬性沒有對應(yīng)值會(huì)被賦值為nil铡恕,就會(huì)導(dǎo)致crash琢感,所以我們要加一個(gè)判斷,獲取到Value時(shí)探熔,才給模型中屬性賦值驹针。

NSDictionary *dict = @{
                           @"name" : @"xiaoming",
                           @"age"  : @25,
                           @"weight" : @"60kg",
                           @"height" : @1.81
                           };
#import "NSObject+Model.h"
#import <objc/message.h>

@implementation NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict
{
    // 創(chuàng)建對應(yīng)的對象
    id objc = [[self alloc] init];
    
    // 成員變量個(gè)數(shù)
    unsigned int count = 0;
    // 獲取類中的所有成員變量
    Ivar *ivars = class_copyIvarList(self, &count);
    
    // 遍歷所有成員變量
    for (int i = 0; i < count; i++) {
        // 根據(jù)角標(biāo),從數(shù)組取出對應(yīng)的成員變量(Ivar:成員變量,以下劃線開頭)
        Ivar ivar = ivars[i];
        
        // 獲取成員變量名字
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開始截取)
        NSString *key = [ivarName substringFromIndex:1];
        
        // 根據(jù)成員屬性名去字典中查找對應(yīng)的value
        id value = dict[key];

        if (value) {  
            // 給模型中屬性賦值
            [objc setValue:value forKey:key];
        }
    }
    // 釋放ivars
    free(ivars);
    
    return objc;
}
@end

2.模型中嵌套模型(模型屬性是另外一個(gè)模型對象)

利用runtime的ivar_getTypeEncoding 方法獲取模型對象類型诀艰,對該模型對象類型再進(jìn)行字典轉(zhuǎn)模型柬甥,也就是進(jìn)行遞歸,需要注意的是要排除系統(tǒng)的對象類型其垄,例如NSString苛蒲。

NSDictionary *dict2 = @{
                           @"name" : @"xiaoming",
                           @"age"  : @25,
                           @"body" :@{
                                      @"weight" : @"65kg",
                                      @"height" : @1.82
                                     }
                           };
// runtime字典轉(zhuǎn)模型二級(jí)轉(zhuǎn)換:字典->字典;如果字典中還有字典绿满,也需要把對應(yīng)的字典轉(zhuǎn)換成模型
if ([value isKindOfClass:[NSDictionary class]]) {

    // 獲取成員變量類型
    NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

    // 替換: @\"User\" -> User
    ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
    ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];

    if (![ivarType hasPrefix:@"NS"]) {
        // 字典轉(zhuǎn)換成模型,根據(jù)字符串類名生成類對象
        Class modelClass = NSClassFromString(ivarType);
        if (modelClass) { // 有對應(yīng)的模型才需要轉(zhuǎn)
            // 把字典轉(zhuǎn)模型
            value = [modelClass modelWithDict:value];
        }
    }
}

3.數(shù)組中裝著模型(模型的屬性是一個(gè)數(shù)組臂外,數(shù)組中是一個(gè)個(gè)模型對象)

攔截到模型的數(shù)組屬性,進(jìn)而對數(shù)組中每個(gè)模型遍歷并字典轉(zhuǎn)模型喇颁,但是我們不知道數(shù)組中的模型都是什么類型漏健,需要聲明一個(gè)方法,該方法目的不是讓其調(diào)用橘霎,而是讓其實(shí)現(xiàn)并返回模型的類型蔫浆。

NSDictionary *dict3 = @{
                            @"name" : @"xiaoming",
                            @"age"  : @25,
                            @"body" :@{
                                    @"weight" : @"65kg",
                                    @"height" : @1.82
                                    },
                            @"children" : @[
                                    @{
                                        @"sex" : @"男",
                                        @"love" : @"籃球",
                                        },
                                    @{
                                        @"sex" : @"nv",
                                        @"love" : @"鋼琴",
                                        }
                                    ],
                            };
// runtime字典轉(zhuǎn)模型三級(jí)轉(zhuǎn)換:字典->數(shù)組->字典;NSArray中也是字典姐叁,把數(shù)組中的字典轉(zhuǎn)換成模型.
if ([value isKindOfClass:[NSArray class]]) {
    // 判斷對應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
    // arrayContainModelClass 提供一個(gè)協(xié)議瓦盛,只要遵守這個(gè)協(xié)議的類,都能把數(shù)組中的字典轉(zhuǎn)模型
    if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

        // 轉(zhuǎn)換成id類型七蜘,就能調(diào)用任何對象的方法
        id idSelf = self;

        // 獲取數(shù)組中字典對應(yīng)的模型
        NSString *type =  [idSelf arrayContainModelClass][key];

        // 生成模型
        Class classModel = NSClassFromString(type);
        NSMutableArray *arrM = [NSMutableArray array];
        // 遍歷字典數(shù)組谭溉,生成模型數(shù)組
        for (NSDictionary *dict in value) {
            // 字典轉(zhuǎn)模型
            id model =  [classModel modelWithDict:dict];
            [arrM addObject:model];
        }

        // 把模型數(shù)組賦值給value
        value = arrM;
    }
}
#import <Foundation/Foundation.h>

@protocol ModelDelegate <NSObject>

@optional
/**
 提供一個(gè)協(xié)議,只要遵守這個(gè)協(xié)議的類橡卤,都能把數(shù)組中的字典轉(zhuǎn)模型
 */
+ (NSDictionary *)arrayContainModelClass;

@end

@interface NSObject (Model)

/**
 dict -> model
 利用runtime 遍歷模型中所有屬性扮念,根據(jù)模型中屬性去字典中取出對應(yīng)的value給模型屬性賦值
 */
+ (instancetype)modelWithDict:(NSDictionary *)dict;

@end

實(shí)現(xiàn)協(xié)議類:

+ (NSDictionary *)arrayContainModelClass
{
    // 數(shù)組屬性 : 數(shù)組中的類名
    return @{@"children" : @"MJChild"};
}

不忙的時(shí)候,就整理知識(shí)碧库,經(jīng)過幾天時(shí)間的努力柜与,終于寫好了。途中參考大量資料嵌灰,并通過Demo驗(yàn)證其正確性弄匕,也算對自己的一次全面級(jí)的學(xué)習(xí)與復(fù)習(xí)。
下一篇沽瞭,會(huì)深入了解Runtime底層語言迁匠。
I’m not perfect. But I keep trying.

參考文獻(xiàn):

蘋果官方文檔
OC最實(shí)用的runtime總結(jié)
讓你快速上手Runtime
runtime詳解
iOS 模式詳解—
iOS Runtime詳解
裝逼技術(shù)RunTime的總結(jié)篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子城丧,更是在濱河造成了極大的恐慌延曙,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亡哄,死亡現(xiàn)場離奇詭異枝缔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚊惯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門愿卸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人截型,你說我怎么就攤上這事趴荸。” “怎么了菠劝?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵赊舶,是天一觀的道長。 經(jīng)常有香客問我赶诊,道長笼平,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任舔痪,我火速辦了婚禮寓调,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锄码。我一直安慰自己夺英,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布滋捶。 她就那樣靜靜地躺著痛悯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪重窟。 梳的紋絲不亂的頭發(fā)上载萌,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音巡扇,去河邊找鬼扭仁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厅翔,可吹牛的內(nèi)容都是我干的乖坠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刀闷,長吁一口氣:“原來是場噩夢啊……” “哼熊泵!你這毒婦竟也來了仰迁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤顽分,失蹤者是張志新(化名)和其女友劉穎轩勘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怯邪,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年花墩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悬秉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冰蘑,死狀恐怖和泌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祠肥,我是刑警寧澤武氓,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站仇箱,受9級(jí)特大地震影響县恕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剂桥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一忠烛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧权逗,春花似錦美尸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堪滨,卻和暖如春胯陋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背椿猎。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工惶岭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人犯眠。 一個(gè)月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓按灶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親筐咧。 傳聞我的和親對象是個(gè)殘疾皇子鸯旁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354