iOS Runtime全面解析

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

一. 消息機制

1.1. 消息傳遞

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

先來熟悉下類的基本結(jié)構(gòu),在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; // 類的版本信息,初始化默認(rèn)為0,可以通過runtime函數(shù)class_setVersion和class_getVersion進行修改、讀取
  long info   OBJC2_UNAVAILABLE; // 一些標(biāo)識信息,如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 的一些標(biāo)志位有關(guān),如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)典圖來說明:

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

 [obj  test];

轉(zhuǎn)化為匯編代碼:

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,并且方法解析也沒有處理,那么則進入消息轉(zhuǎn)發(fā)流程:

  imp = (IMP)_objc_msgForward_impcache;

1.2. 消息轉(zhuǎn)發(fā)

在調(diào)用對象拿到對應(yīng)的selector之后,如果自己無法執(zhí)行這個方法,那么該條消息要被轉(zhuǎn)發(fā)?;蛘吲R時動態(tài)的添加方法實現(xiàn)。如果轉(zhuǎ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返回值設(shè)為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];
}

這樣就可以得到結(jié)果:

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

關(guān)于消息轉(zhuǎn)發(fā),希望您對Type Encodings 、NSMethodSignature 、NSInvocation已經(jīng)有基本的認(rèn)知,可查看本人呢另一篇文章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)典圖:


消息轉(zhuǎn)發(fā)也是我們處理unrecognized selector crash 的主要方案,減少對應(yīng)的崩潰。

1.3. 關(guān)于NSProxy

說到消息轉(zhuǎn)發(fā)這一問題,NSProxy才是消息轉(zhuǎn)發(fā)、消息分發(fā)的終極答案。

對比上面的一套消息查找過程,NSProxy就簡單多了,接收到 unkonwn selector后,直接調(diào)用- (NSMethodSignature *)methodSignatureForSelector:- (void)forwardInvocation:進行消息轉(zhuǎn)發(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 應(yīng)用

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

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

2.1. 關(guān)聯(lián)對象

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

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

另一方面,從Category的結(jié)構(gòu)體組成也能證明這一點:

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, 屬性是要自動合成相關(guān)的成員變量的,而其明顯不具備這一特點。so,該如何做呢 ? 當(dāng)然還是回到Runtime。

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

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

// 獲取關(guān)聯(lián)的對象 getter
id objc_getAssociatedObject(id object, const void *key); 
// 移除關(guān)聯(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編程具有不可動搖的核心地位,修改原有方法指向的特性使其能夠十分出色完成以下任務(wù):

  • hook系統(tǒng)方法,例如hook系統(tǒng)字體設(shè)置動態(tài)修改不同屏幕下字體大小,hook系統(tǒng)生命周期方法達到埋點統(tǒng)計的目的。
  • debug過程中hook原方法來進行bug修復(fù)。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:);
    // 原方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
    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:);
     // 原方法結(jié)構(gòu)體和替換方法結(jié)構(gòu)體
     Method originalMethod = class_getInstanceMethod(class, originalSelector);
     Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
     // 如果當(dāng)前類沒有原方法的實現(xiàn)IMP,先調(diào)用class_addMethod來給原方法添加默認(rèn)的方法實現(xiàn)IMP
     BOOL didAddMethod = class_addMethod(class,
                       originalSelector,
                       method_getImplementation(swizzledMethod),
                       method_getTypeEncoding(swizzledMethod));

   
     if (didAddMethod) {
     // 添加方法實現(xiàn)IMP成功后,修改替換方法結(jié)構(gòu)體內(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);
     }
  });

}

當(dāng)然如果我們重寫這個方法,也是可以的。

為什么會這樣呢?

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

2.3. 字典和模型的自動轉(zhuǎn)換

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

具體方案如下:

+ (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];
        // 二級轉(zhuǎn)換:判斷下value是否是字典,如果是,字典轉(zhuǎn)換層對應(yīng)的模型
        // 并且是自定義對象才需要轉(zhuǎn)換
        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三部曲,相信就能輕松理解這段代碼,就不多敘。

三. 總結(jié)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容