iOS Runtime全面解析

Object-C采用"消息結構"而非”函數(shù)調(diào)用“昵济。對于函數(shù)調(diào)用的語言,是由編譯器決定的。而消息結構的語言访忿,其運行所執(zhí)行的代碼由運行環(huán)境來決定瞧栗。 而這個運行環(huán)境,就是Runtime海铆。

一. 消息機制

1.1. 消息傳遞

消息機制是Runtime的核心迹恐,方法調(diào)用的過程可以看做是消息傳遞的過程。

先來熟悉下類的基本結構卧斟,在iOS中殴边,基本上所有類都直接或者間接繼承于NSObject(也有NSProxy這種例外),那么來看下NSObject:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

NSObject中持有一個Class類型的isa指針,那么這個Class是什么呢珍语?來看一下:

typedef struct objc_class *Class;

struct objc_class {
  Class _Nonnull isa  OBJC_ISA_AVAILABILITY; // 指向metaclass
  Class _Nullable super_class   OBJC2_UNAVAILABLE; // 指向其父類
  const char * _Nonnull name    OBJC2_UNAVAILABLE; // 類名
  long version    OBJC2_UNAVAILABLE; // 類的版本信息锤岸,初始化默認為0,可以通過runtime函數(shù)class_setVersion和class_getVersion進行修改板乙、讀取
  long info   OBJC2_UNAVAILABLE; // 一些標識信息,如CLS_CLASS (0x1L) 表示該類為普通 class 是偷,其中包含對象方法和成員變量;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法;
  long instance_size   OBJC2_UNAVAILABLE; // 該類的實例變量大小(包括從父類繼承下來的實例變量);
  struct objc_ivar_list * _Nullable ivars  OBJC2_UNAVAILABLE; // 用于存儲每個成員變量的地址
  struct objc_method_list * _Nullable * _Nullable methodLists  OBJC2_UNAVAILABLE; 
  // 與 info 的一些標志位有關,如CLS_CLASS (0x1L),則存儲對象方法募逞,如CLS_META (0x2L)蛋铆,則存儲類方法;
  struct objc_cache * _Nonnull cache  OBJC2_UNAVAILABLE; //指向最近使用的方法的指針,用于提升效率凡辱;
  struct objc_protocol_list * _Nullable protocols   OBJC2_UNAVAILABLE; // 存儲該類遵守的協(xié)議
}

可以看到戒职,objc_class中也有多個元素栗恩,除了類型父類等能夠理解顧名思義的元素透乾,特別需要注意的是isacache兩個元素。cache是將用過的方法存儲到其內(nèi)磕秤,優(yōu)先查找乳乌,是典型的時空裝換。而對于類的isa, 它是指向元類的市咆,也就是說:

mateClass(元類)生成Class(類/類對象), Class(類)生成obj(對象)汉操。用一張經(jīng)典圖來說明:

有了以上的基礎,那么消息傳遞就會容易理解很多蒙兰。例如我們調(diào)用一個實例方法:

 [obj  test];

轉化為匯編代碼:

objc_msgSend(obj,sel_registerName("test"));

接下來會調(diào)用_class_lookupMethodAndLoadCache3方法磷瘤,看下其具體實現(xiàn):

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{        
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
 IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    runtimeLock.lock();
    checkIsKnownClass(cls);
    if (!cls->isRealized()) {
        realizeClass(cls);
    }
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
    }
 retry:    
    runtimeLock.assertLocked();
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        triedResolver = YES;
        goto retry;
    }
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);
 done:
    runtimeLock.unlock();
    return imp;
}

即開始先從cache查找:

if (cache) {
    imp = cache_getImp(cls, sel);
    if (imp) return imp;
}

如果緩存命中,直接返回imp搜变。如果沒有命中采缚,繼續(xù)往下走,先判斷類有沒有加載到內(nèi)存挠他,如果沒有扳抽,先加載類:

  checkIsKnownClass(cls);
  if (!cls->isRealized()) {
     realizeClass(cls);
  }

判斷是否實現(xiàn)了initialize,如果有實現(xiàn),先調(diào)用initialize

if (initialize  &&  !cls->isInitialized()) {
    runtimeLock.unlock();
    _class_initialize (_class_getNonMetaClass(cls, inst));
    runtimeLock.lock();
}

在類對象的方法列表查找imp:

 Method meth = getMethodNoSuper_nolock(cls, sel);
      if (meth) {
         log_and_fill_cache(cls, meth->imp, sel, inst, cls);
         imp = meth->imp;
         goto done;
      }
 }

如果沒有找到贸呢,繼續(xù)在父類的緩存的方法列表中查找imp镰烧。

   unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }

imp還沒有找到,則嘗試做一次動態(tài)方法解析:

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);//這里做一次動態(tài)方法解析楞陷。
        runtimeLock.lock();
        triedResolver = YES;
        goto retry;
    }

最終沒有找到imp怔鳖,并且方法解析也沒有處理,那么則進入消息轉發(fā)流程:

  imp = (IMP)_objc_msgForward_impcache;

1.2. 消息轉發(fā)

在調(diào)用對象拿到對應的selector之后固蛾,如果自己無法執(zhí)行這個方法败砂,那么該條消息要被轉發(fā)∥呵Γ或者臨時動態(tài)的添加方法實現(xiàn)昌犹。如果轉發(fā)到最后依舊沒法處理,程序就會崩潰览芳。

如以下例子:

新建一個Person類繼承于NSObject,并聲明一個msgTest方法(不實現(xiàn));

@interface Person : NSObject

- (void)msgTest;

@end

調(diào)用該方法:

- (void)viewDidLoad {
    [super viewDidLoad];
    Person * p = [Person new];
    [p msgTest];
}

此時我們將項目跑起來就會發(fā)現(xiàn)斜姥,項目是能通過編譯的,但是會崩潰掉:

-[Person msgTest]: unrecognized selector sent to instance 0x6000020543e0

在方法在調(diào)用時沧竟,系統(tǒng)會查看這個對象能否接收這個消息(沒有實現(xiàn)這個方法)铸敏,如果不能接收,就會調(diào)用下面這幾個方法悟泵,會采用拯救模式杈笔,給你“補救”的機會。

第一次補救: 動態(tài)方法解析
/*
cls:要添加方法的類
name:選擇器
imp:方法實現(xiàn),IMP在objc.h中的定義是:typedef id (*IMP)(id, SEL, ...);該方法至少有兩個參數(shù),self(id)和_cmd(SEL)
types:方法,參數(shù)和返回值的描述,"v@:"表示返回值為void,沒有參數(shù)
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(msgTest)){
        return   class_addMethod([self class],sel, (IMP)reTest, "v@:");
  }
    return [super resolveInstanceMethod:sel];
}

void reTest(id self, SEL _cmd) {
    NSLog(@"test");
}

可看到打印數(shù)據(jù):

learn[47237:860941] test

注: resolveInstanceMethod處理對象方法糕非,resolveClassMethod處理類方法蒙具。

第二次補救: 消息重定向

我們繼續(xù)以實例方法舉例:

創(chuàng)建一個新的類RePerson,該類包含有msgTest的實現(xiàn)方法朽肥。

#import "RePerson.h"

@implementation RePerson

- (void)msgTest{
    NSLog(@"rePerson");
}

@end

Person類中進行下兩步操作:

  1. resolveInstanceMethod返回值設為NO禁筏。
  2. forwardingTargetForSelector返回值為RePerson對象。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(msgTest)){
        return   NO;
    }
    return [super resolveInstanceMethod:sel];
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(msgTest)){
        return  [RePerson new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

這樣就可以得到結果:

 learn[47519:906599] rePerson
第三次補救: 消息轉發(fā)

關于消息轉發(fā)衡招,希望您對Type Encodings 篱昔、NSMethodSignature 、NSInvocation已經(jīng)有基本的認知始腾,可查看本人呢另一篇文章Type Encodings 州刽、NSMethodSignature 、NSInvocation三部曲浪箭。

也是改變調(diào)用對象,使該消息在新對象上調(diào)用;不同是forwardInvocation方法帶有一個NSInvocation對象,這個對象保存了這個方法調(diào)用的所有信息,包括SEL穗椅,參數(shù)和返回值描述等。

同樣的山林,我們利用上文中描述的RePerson類房待,實現(xiàn)以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    if (anInvocation.selector == @selector(msgTest)){
        [anInvocation invokeWithTarget:[RePerson new]];
        return;
    }
    [super forwardInvocation:anInvocation];
}

同樣的邢羔,我們也可以拿到如下答案:

learn[47574:911006] rePerson

經(jīng)典圖:


消息轉發(fā)也是我們處理unrecognized selector crash 的主要方案,減少對應的崩潰桑孩。

1.3. 關于NSProxy

說到消息轉發(fā)這一問題拜鹤,NSProxy才是消息轉發(fā)、消息分發(fā)的終極答案流椒。

對比上面的一套消息查找過程敏簿,NSProxy就簡單多了,接收到 unkonwn selector后宣虾,直接調(diào)用- (NSMethodSignature *)methodSignatureForSelector:- (void)forwardInvocation:進行消息轉發(fā)惯裕。看下YYWeakProxy的源碼:

@implementation YYWeakProxy

- (instancetype)initWithTarget:(id)target {
   _target = target;
   return self;
}

+ (instancetype)proxyWithTarget:(id)target {
   return [[YYWeakProxy alloc] initWithTarget:target];
}

- (id)forwardingTargetForSelector:(SEL)selector {
   return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
   void *null = NULL;
   [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
   return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
   return [_target respondsToSelector:aSelector];
}

- (BOOL)isEqual:(id)object {
   return [_target isEqual:object];
}

- (NSUInteger)hash {
   return [_target hash];
}

- (Class)superclass {
   return [_target superclass];
}

- (Class)class {
   return [_target class];
}

- (BOOL)isKindOfClass:(Class)aClass {
   return [_target isKindOfClass:aClass];
}

- (BOOL)isMemberOfClass:(Class)aClass {
   return [_target isMemberOfClass:aClass];
}

- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
   return [_target conformsToProtocol:aProtocol];
}

- (BOOL)isProxy {
   return YES;
}

- (NSString *)description {
   return [_target description];
}

- (NSString *)debugDescription {
   return [_target debugDescription];
}

@end

其實就是簡單的實現(xiàn)這兩種方法而已绣硝。

它的主要功能之一就是避免循環(huán)引用:

 @implementation MyView {
    NSTimer *_timer;
 }
 
 - (void)initTimer {
    YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
    _timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES];
 }
 
 - (void)tick:(NSTimer *)timer {...}
 @end

如上例子蜻势, MyView持有Timer, Timer強引用Proxy, Proxy雖然能發(fā)送消息到MyView卻不會形成強引用。

二. Runtime 應用

Runtime的是iOS中的高頻詞鹉胖,具體的使用大致分為以下幾個類別:

  • 關聯(lián)對象(Objective-C Associated Objects)添加對象握玛。
  • 方法交換Method Swizzling
  • 字典和模型的自動轉換甫菠。

2.1. 關聯(lián)對象

首先拋出一個問題:分類Category為什么不能直接添加屬性挠铲。

從邏輯角度來說,Category本來就不是一個真實的類寂诱,是在Runtime期間拂苹,動態(tài)的為相關類添加方法。在編譯期間連相關對象都沒拿到痰洒,如何添加屬性瓢棒?

另一方面,從Category的結構體組成也能證明這一點:

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods; // 對象方法
    struct method_list_t *classMethods; // 類方法
    struct protocol_list_t *protocols; // 協(xié)議
    struct property_list_t *instanceProperties; // 屬性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

雖然其中包括了屬性的list,但是并不包含成員變量的list, 屬性是要自動合成相關的成員變量的带迟,而其明顯不具備這一特點音羞。so囱桨,該如何做呢 ? 當然還是回到Runtime仓犬。

Runtime提供了三個函數(shù)進行屬性關聯(lián):

// 關聯(lián)對象 setter
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
// objec: 被關聯(lián)對象。key:關聯(lián)key, 唯一標識舍肠。 value:關聯(lián)的對象搀继。policy: 內(nèi)存管理的策略。

// 獲取關聯(lián)的對象 getter
id objc_getAssociatedObject(id object, const void *key); 
// 移除關聯(lián)對象  delloc
void objc_removeAssociatedObjects(id object);

內(nèi)存策略:

OBJC_ASSOCIATION_ASSIGN,    //等價于 @property(assign)翠语。
OBJC_ASSOCIATION_RETAIN_NONATOMIC,  //等價于 @property(strong, nonatomic)叽躯。
OBJC_ASSOCIATION_COPY_NONATOMIC,   //等價于 @property(copy, nonatomic)。
OBJC_ASSOCIATION_RETAIN  //等價于@property(strong,atomic)肌括。
OBJC_ASSOCIATION_COPY   //等價于@property(copy, atomic)点骑。

如我們給一個UIViewController分類添加一個params字典用戶接受傳遞過來的參數(shù):

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIViewController (Base)

@property (nonatomic, strong) NSDictionary * params;

@end

NS_ASSUME_NONNULL_END

#import "UIViewController+Base.h"
#import <objc/runtime.h>

static const void * jParamsKey = &jParamsKey;

@implementation UIViewController (Base)

- (void)setParams:(NSDictionary *)params{
    objc_setAssociatedObject(self, jParamsKey, params, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSDictionary *)params{
    return objc_getAssociatedObject(self, jParamsKey);
}

@end

2.2. 方法交換 (Method Swizzling)

Method Swizzling 被稱為黑魔法, 在iOS編程具有不可動搖的核心地位,修改原有方法指向的特性使其能夠十分出色完成以下任務:

  • hook系統(tǒng)方法黑滴,例如hook系統(tǒng)字體設置動態(tài)修改不同屏幕下字體大小憨募,hook系統(tǒng)生命周期方法達到埋點統(tǒng)計的目的。
  • debug過程中hook原方法來進行bug修復袁辈。hook 例如NSArrayindexof去防崩潰菜谣。
  • 實現(xiàn)KVO類的觀察者方案。

先看代碼吧晚缩,如果我們實現(xiàn)UIFont的動態(tài)方案:

#import "UIFont+Adapt.h"

@implementation UIFont (Adapt)

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self exchangeMethod];
    });
}

+ (void)exchangeMethod{
    Class class = [self class];
    SEL originalSelector = @selector(systemFontOfSize:);
    SEL swizzledSelector = @selector(runTimeFitFont:);
    Method systemMethod = class_getClassMethod(class, originalSelector );
    Method swizzledMethod  = class_getClassMethod(class, swizzledSelector);
    method_exchangeImplementations(systemMethod, swizzledMethod);
}

+ (UIFont *)runTimeFitFont:(CGFloat)fontSize{
    UIFont *fitFont = nil;
    //這里并不會造成循環(huán)調(diào)用,方法已經(jīng)被交換
    fitFont = [UIFont runTimeFitFont:fontSize * (Main_Screen_Width / 375 )];
    return fitFont;
}

@end

這里解釋下這些代碼:

一般情況下尾膊,都會寫一個分類來實現(xiàn)Method Swizzling。 一般情況下會在load方法里調(diào)用荞彼,保證在該方法調(diào)用之前冈敛,已經(jīng)完成了方法交換。

load方法在不同系統(tǒng)下有不同表現(xiàn)鸣皂,在iOS10或者其它情況下莺债,會出現(xiàn)多次調(diào)用的情況,所以使用dispatch_once方案保證方法交換只實現(xiàn)一次签夭。

hook完成后齐邦,我們調(diào)用原方法,最終就會調(diào)用到交換后的方法第租,而在交換方法如需調(diào)用原方法措拇,類似上面的本來該調(diào)用systemFontOfSize:的,但是systemFontOfSize:已經(jīng)被交換了慎宾,所以調(diào)用runTimeFitFont:(CGFloat)fontSize就是調(diào)用systemFontOfSize:丐吓,并不會引起循環(huán)調(diào)用。

另一個需要注意點是在hook父類的方法時候存在的問題趟据,比如我們有一個HookViewController繼承于 BaseViewController繼承于UIViewController券犁,如果我們想hook它的viewDidAppear,如果我們直接hook:

+ (void)load{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Class class = [self class];
    // 原方法名和替換方法名
    SEL originalSelector = @selector(viewDidAppear:);
    SEL swizzledSelector = @selector(swizzle_viewDidAppear:);
    // 原方法結構體和替換方法結構體
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    // 調(diào)用交互兩個方法的實現(xiàn)
    method_exchangeImplementations(originalMethod, swizzledMethod);
  });
}

就會報錯:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[JTabbarController swizzle_viewDidAppear:]: unrecognized selector sent to instance 0x7fb191811400'

修改成:

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
     Class class = [self class];
     // 原方法名和替換方法名
     SEL originalSelector = @selector(viewDidAppear:);
     SEL swizzledSelector = @selector(swizzle_viewDidAppear:);
     // 原方法結構體和替換方法結構體
     Method originalMethod = class_getInstanceMethod(class, originalSelector);
     Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
     // 如果當前類沒有原方法的實現(xiàn)IMP汹碱,先調(diào)用class_addMethod來給原方法添加默認的方法實現(xiàn)IMP
     BOOL didAddMethod = class_addMethod(class,
                       originalSelector,
                       method_getImplementation(swizzledMethod),
                       method_getTypeEncoding(swizzledMethod));

   
     if (didAddMethod) {
     // 添加方法實現(xiàn)IMP成功后粘衬,修改替換方法結構體內(nèi)的方法實現(xiàn)IMP和方法類型編碼TypeEncoding
       class_replaceMethod(class,
                 swizzledSelector,
                 method_getImplementation(originalMethod),
                 method_getTypeEncoding(originalMethod));

     } else {
     // 添加失敗,調(diào)用交互兩個方法的實現(xiàn)
       method_exchangeImplementations(originalMethod, swizzledMethod);
     }
  });

}

當然如果我們重寫這個方法咳促,也是可以的稚新。

為什么會這樣呢?

根據(jù)方法的的查找路徑跪腹,沒有重寫的話實質(zhì)會去調(diào)用父類的方法褂删,但是父類沒有實現(xiàn)Imp,就會失敗。

2.3. 字典和模型的自動轉換

根據(jù)上文冲茸,我們已經(jīng)明白屯阀, 類的結構體中包含了成員變量的list, 那么在這個前提下缅帘,我們就很輕松的做到字典到模型或者說json到模型的轉換。

具體方案如下:

+ (instancetype)modelWithDict:(NSDictionary *)dict{
    id objc = [[self alloc] init];
    //1.獲取成員變量
    unsigned int count = 0;
    //獲取成員變量數(shù)組
    Ivar *ivarList = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        //獲取成員變量
        Ivar ivar = ivarList[i];
        //獲取成員變量名稱
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //獲取成員變量類型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
        //獲取key
        NSString *key = [ivarName substringFromIndex:1];
        id value = dict[key];
        // 二級轉換:判斷下value是否是字典,如果是,字典轉換層對應的模型
        // 并且是自定義對象才需要轉換
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]){
            //獲取class
            Class modelClass = NSClassFromString(ivarType);
            value = [modelClass modelWithDict:value];
        }
        if (value) {
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}

搭配Type Encodings 难衰、NSMethodSignature 股毫、NSInvocation三部曲,相信就能輕松理解這段代碼召衔,就不多敘铃诬。

三. 總結

這篇文章算是寫的比較快的,大致就是想到哪就寫一寫苍凛,Runtime這個話題其實也有無數(shù)人寫過了趣席,我只是想用自己的思路把這個話題順一下,有什么問題醇蝴,歡迎留言宣肚。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市悠栓,隨后出現(xiàn)的幾起案子霉涨,更是在濱河造成了極大的恐慌,老刑警劉巖惭适,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笙瑟,死亡現(xiàn)場離奇詭異,居然都是意外死亡癞志,警方通過查閱死者的電腦和手機往枷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凄杯,“玉大人错洁,你說我怎么就攤上這事〗渫唬” “怎么了屯碴?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長膊存。 經(jīng)常有香客問我导而,道長,這世上最難降的妖魔是什么膝舅? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任嗡载,我火速辦了婚禮,結果婚禮上仍稀,老公的妹妹穿的比我還像新娘。我一直安慰自己埂息,他們只是感情好技潘,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布遥巴。 她就那樣靜靜地躺著,像睡著了一般享幽。 火紅的嫁衣襯著肌膚如雪铲掐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天值桩,我揣著相機與錄音摆霉,去河邊找鬼。 笑死奔坟,一個胖子當著我的面吹牛携栋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咳秉,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼婉支,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了澜建?” 一聲冷哼從身側響起向挖,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎炕舵,沒想到半個月后何之,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡咽筋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年帝美,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晤硕。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡悼潭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舞箍,到底是詐尸還是另有隱情舰褪,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布疏橄,位于F島的核電站占拍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捎迫。R本人自食惡果不足惜晃酒,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窄绒。 院中可真熱鬧贝次,春花似錦、人聲如沸彰导。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至山析,卻和暖如春堰燎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笋轨。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工秆剪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像胞谭,于是被迫代替她去往敵國和親轿偎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容