iOS Runtime詳解及應(yīng)用場景

一 runtime簡介
二 Class的結(jié)構(gòu)
三 isa指針詳解
四 method詳解
五 方法調(diào)用及消息轉(zhuǎn)發(fā)流程
六 runtime常用api
七 runtime開發(fā)中具體應(yīng)用

一 runtime簡介

定義:所謂運(yùn)行時, 就是盡可能地把決定從編譯器推遲到運(yùn)行期, 就是盡可能地做到動態(tài). 只是在運(yùn)行的時候才會去確定對象的類型和方法的. 因此利用Runtime機(jī)制可以在程序運(yùn)行時動態(tài)地修改類和對象中的所有屬性和方法.
Objective-C中調(diào)用對象的方法時, 會向該對象發(fā)送一條消息, runtime根據(jù)該消息做出反應(yīng).
Runtime是一套比較底層的純C語言的API, Objective-C是運(yùn)行在Runtime上的, 因此在Runtime中動態(tài)添加和實(shí)現(xiàn)一些非常強(qiáng)大的功能也就不足為奇了.
在Objective-C代碼中使用Runtime, 需要引入<#import <objc/runtime.h>

總結(jié)說明:
runtime是什么?

runtime是一組APi,我們平時開發(fā)也用到很多API,所以runtime并沒啥稀奇的,只不過使用c語言寫的,GCD也是C語言寫的API庫。

runtime API干啥用的?

程序運(yùn)行時動態(tài)地修改類和對象中的所有屬性和方法,就是用來在程序運(yùn)行是改變類的屬性和行為的,就像GCD就是用來操作線程的。


二 Class的結(jié)構(gòu)

既然是一組API來操作類的,首先我們就先來了解下類的本質(zhì)是什么,然后再看它是怎么操作它的。

2.1我們看下類的本質(zhì)定義

objc 源碼地址 objc-runtime-new.h

class 結(jié)構(gòu)圖

我們看出來,它實(shí)際上就是用一個結(jié)構(gòu)體來定義的,我們想想一個類它有什么,它首先有,屬性,方法,然后有父類,他自己的標(biāo)示等等。這個結(jié)構(gòu)體里面基本都包含了。
接下來讓我們具體解釋一下這個結(jié)構(gòu)體里面的每一項(xiàng)內(nèi)容

2.1.1 ISA
Class ISA;  

objc_class 中也有一個 isa 指針,這說明 Objc 類本身也是一個對象。為了處理類和對象的關(guān)系,Runtime 庫創(chuàng)建了一種叫做 Meta Class(元類) 的東西,類對象所屬的類就叫做元類。Meta Class 表述了類對象本身所具備的元數(shù)據(jù)。

我們所熟悉的類方法,就源自于 Meta Class。我們可以理解為類方法就是類對象的實(shí)例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關(guān)的元類。

當(dāng)你發(fā)出一個類似 NSObject alloc 的消息時,實(shí)際上,這個消息被發(fā)送給了一個類對象(Class Object),這個類對象必須是一個元類的實(shí)例,而這個元類同時也是一個根元類(Root Meta Class)的實(shí)例。所有元類的 isa 指針最終都指向根元類。 下一節(jié)我們再詳細(xì)介紹。

2.1.2 cache_t
struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;

Cache 為方法調(diào)用的性能進(jìn)行優(yōu)化,每當(dāng)實(shí)例對象接收到一個消息時,它不會直接在 isa 指針指向的類的方法列表中遍歷查找能夠響應(yīng)的方法,因?yàn)槊看味家檎倚侍土?,而是?yōu)先在 Cache 中查找。
Runtime 系統(tǒng)會把被調(diào)用的方法存到 Cache 中,如果一個方法被調(diào)用,那么它有可能今后還會被調(diào)用,下次查找的時候就會效率更高。就像計(jì)算機(jī)組成原理中 CPU 繞過主存先訪問 Cache 一樣。
這個cache主要是方法調(diào)用的優(yōu)化,別的沒啥。

2.1.3 class_rw_t
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;     // 方法列表
    property_array_t properties;// 屬性列表
    protocol_array_t protocols; // 協(xié)議列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

class_rw_t里面的methods、properties、protocols是二維數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容,二位數(shù)組存儲是為的更好的擴(kuò)展新加的方法屬性或協(xié)議。類和分類的信息都存儲在這個結(jié)構(gòu)體里面

2.1.3 class_ro_t
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; //實(shí)例對象占用內(nèi)存空間大小
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name; //類名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars; //成員變量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一維數(shù)組,是只讀的,類的成員變量是存放在這里。
類的基本結(jié)構(gòu)大致就這些,接下來我們要詳細(xì)了解一下ISA指針,很多方面的東西都牽扯到它。

三 isa指針詳解
3.1

在arm64架構(gòu)之前,isa就是一個普通的指針,存儲著Class、Meta-Class對象的內(nèi)存地址
從arm64架構(gòu)開始,對isa進(jìn)行了優(yōu)化,變成了一個共用體(union)結(jié)構(gòu),還使用位域來存儲更多的信息
objc-private.h

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

isa.h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

ISA_BITFIELD ISA位域信息解釋
nonpointer
0,代表普通的指針,存儲著Class、Meta-Class對象的內(nèi)存地址
1,代表優(yōu)化過,使用位域存儲更多的信息
has_assoc
是否有設(shè)置過關(guān)聯(lián)對象,如果沒有,釋放時會更快
has_cxx_dtor
是否有C++的析構(gòu)函數(shù)(.cxx_destruct),如果沒有,釋放時會更快
shiftcls
存儲著Class、Meta-Class對象的內(nèi)存地址信息
magic
用于在調(diào)試時分辨對象是否未完成初始化
weakly_referenced
是否有被弱引用指向過,如果沒有,釋放時會更快
deallocating
對象是否正在釋放
extra_rc
里面存儲的值是引用計(jì)數(shù)器減1
has_sidetable_rc
引用計(jì)數(shù)器是否過大無法存儲在isa中
如果為1,那么引用計(jì)數(shù)會存儲在一個叫SideTable的類的屬性中
我們可以看到arm64架構(gòu)之后isa 不僅存儲著Class、Meta-Class對象的內(nèi)存地址還存儲著其他更多信息, Class、Meta-Class對象的內(nèi)存地址被存儲在shiftcls 中占用33位
uintptr_t shiftcls : 33
所以我們從一個對象isa指針看到的并不是對象真實(shí)的內(nèi)存地址,我們要做一下位運(yùn)算才能得到真實(shí)的地址要 & MASK.
既然isa存儲這幾種對象的內(nèi)存地址,那我們就來了解下oc的幾種對象。

3.2 Objective-C中的對象

Objective-C中的對象,簡稱OC對象,主要可以分為3種
instance對象(實(shí)例對象)
class對象(類對象)
meta-class對象(元類對象)

3.2.1 實(shí)例對象 instance

instance對象就是通過類alloc出來的對象,每次調(diào)用alloc都會產(chǎn)生新的instance對象

    Person *person1 = [[Person alloc] init];
    Person *person2 = [[Person alloc] init];
    Person *person3 = [[Person alloc] init];
    Person *person4 = [[Person alloc] init];

person1、person2,person3,person4是Person類的instance對象(實(shí)例對象)
它們是不同的四個對象,分別占據(jù)著四塊不同的內(nèi)存

(lldb) po person1
<Person: 0x102805b70>
(lldb) po person2
<Person: 0x102805bc0>
(lldb) po person3
<Person: 0x102805bd0>
(lldb) po person4
<Person: 0x102805be0>

instance對象在內(nèi)存中存儲的信息包括
isa指針,及它自己的成員變量


3.2.2 class對象(類對象)
  Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];
//傳入對象的類對象
Class p1class = [person1 class];
Class p2class = [person2 class];
NSLog(@"p1class:%p\n p2class:%p",p1class,p2class);

22:17:12.098954+0800 test[4172:228782]
p1class:0x100002180
p2class:0x100002180

p1class ,p2class都是Person的class對象(類對象)
它們是同一個對象。每個類在內(nèi)存中有且只有一個class對象
class對象在內(nèi)存中存儲的信息主要包括
isa指針
superclass指針
類的屬性信息(@property)、類的對象方法信息(instance method)//存的是動態(tài)方法,為空
類的協(xié)議信息(protocol)、類的成員變量信息(ivar) //存儲的是空值
......

3.2.3 meta-class對象(元類對象)
  Person *person1 = [[Person alloc] init];
    Person *person2 = [[Person alloc] init];
    //傳入對象的類對象獲取元類對象
    Class p1metaclass = object_getClass([person1 class]);
    Class p2metaclass = object_getClass([person2 class]);
    Boolean ismetaclass = class_isMetaClass(p1metaclass);//判斷是否是元類對象
    NSLog(@"p1class:%p\n p2class:%p",p1metaclass,p2metaclass);

22:10:13.933354+0800 test[4129:225264] p1class:0x1000021a8
 p2class:0x1000021a8


p1metaclass是Person的meta-class對象(元類對象)
每個類在內(nèi)存中有且只有一個meta-class對象
meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣,在內(nèi)存中存儲的信息主要包括
isa指針
superclass指針
類的類方法信息(class method)存的是靜態(tài)方法
......

3.3 isa的與三種對象之間的關(guān)系
  • 實(shí)例對象(instance)的isa指向類對象(class)
  • 類對象(class)的isa指向元類對象meta-class)

他們的關(guān)系圖如下所示:


isa與三種對象的關(guān)系圖

當(dāng)調(diào)用對象方法時,通過instance的isa找到class,最后找到對象方法的實(shí)現(xiàn)進(jìn)行調(diào)用
當(dāng)調(diào)用類方法時,通過class的isa找到meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用

3.4 supperclass講解

supperclass只存在于類對象與元類對象里,它的作用想必大家也能猜到,遇到繼承關(guān)系的時候supperclass就派上用場了,下面我們就分別講解一下,類對象里的supperclass以及元類對象里的supperclass

3.4.1 類對象里的supperclass
@interface Student : Person
@interface Person: NSObject

我們定義了兩個對象的繼承關(guān)系Student繼承Person,Person繼承NSObject,那么他們的類對象關(guān)系圖如下:


類對象里的supperclass關(guān)系圖

當(dāng)Student的instance對象要調(diào)用Person的對象方法時,會先通過isa找到Student的class,然后通過superclass找到Person的class,最后找到對象方法的實(shí)現(xiàn)進(jìn)行調(diào)用

3.4.2 元類對象里的supperclass
@interface Student : Person
@interface Person: NSObject

我們定義了兩個對象的繼承關(guān)系Student繼承Person,Person繼承
NSObject,那么他們的類對象關(guān)系圖如下:


元類對象里的supperclass關(guān)系圖

當(dāng)Student的class要調(diào)用Person的類方法時,會先通過isa找到Student的meta-class,然后通過superclass找到Person的meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用

3.5 isa、superclass總結(jié)
isa supperclass關(guān)系圖總結(jié)

1 instance的isa指向class
2 class的isa指向meta-class
3 meta-class的isa指向基類的meta-class
4 class的superclass指向父類的class
如果沒有父類,superclass指針為nil

5 meta-class的superclass指向父類的meta-class
基類的meta-class的superclass指向基類的class
6 instance調(diào)用對象方法的軌跡
isa找到class,方法不存在,就通過superclass找父類
7 class調(diào)用類方法的軌跡
isa找meta-class,方法不存在,就通過superclass找父類
這個圖已經(jīng)很經(jīng)典了需要大家細(xì)細(xì)品味。

四 method詳解
4.1.1 method_t

上面我們講過的類的結(jié)構(gòu)中,我們可以看到,一個類的一些方法,協(xié)議,屬性等是存在class_rw_t這個結(jié)構(gòu)體中的。

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;
    method_array_t methods;     // 方法列表
    property_array_t properties;// 屬性列表
    protocol_array_t protocols; // 協(xié)議列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

平時用的最多的就是方法調(diào)用了,下面讓我們來深入了解一下method_array_t methods; // 方法列表
先看一下method_array_t是什么
objc-runtime-new.h

class method_array_t : 
    public list_array_tt<method_t, method_list_t> 
{
    typedef list_array_tt<method_t, method_list_t> Super;

 public:
    method_list_t **beginCategoryMethodLists() {
        return beginLists();
    }
    
    method_list_t **endCategoryMethodLists(Class cls);

    method_array_t duplicate() {
        return Super::duplicate<method_array_t>();
    }
};

我們可以看出這個類結(jié)構(gòu)大概是下面這種結(jié)構(gòu)的


method類結(jié)構(gòu)

class_rw_t里面的methods是二維數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容,那么mathod_t內(nèi)部結(jié)構(gòu)是什么樣的,我們來看一下

struct method_t {
    SEL name;   // 函數(shù)名
    const char *types; //編碼(返回值,參數(shù))
    MethodListIMP imp;  //函數(shù)地址

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

由此結(jié)構(gòu)我們可以看出method_t就是對一個函數(shù)的具體描述,包含函數(shù)的名稱,函數(shù)的出參,入?yún)?,以及函?shù)具體地址,那么我們就具體來說一下它這幾個變量

MethodListIMP
// Method lists use process-independent signature for compatibility.
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;

#else

using MethodListIMP = IMP;

#endif
objc.h

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP 代表函數(shù)的具體實(shí)現(xiàn)

SEL
objc.h

typedef struct objc_selector *SEL;

1 SEL代表方法\函數(shù)名,一般叫做選擇器,底層結(jié)構(gòu)跟char *類似
2 可以通過@selector()和sel_registerName()獲得
可以通過sel_getName()和NSStringFromSelector()轉(zhuǎn)成字符串

 SEL speaksel = sel_registerName(@"speak");
 SEL speaksel2 = @selector(speak);

3 不同類中相同名字的方法,所對應(yīng)的方法選擇器是相同的

這個其實(shí)很好理解,同一個類的不同對象,它的類結(jié)構(gòu)都是一樣的,類結(jié)構(gòu)存儲一份就好了,所以它們方法描述也只有一份,所有這個類的對象共用一份方法描述,只是每個對象傳的出參,入?yún)?,不一樣罷了。

types
const char *types 

描述方法參數(shù)類型的字符數(shù)組,types包含了函數(shù)返回值、參數(shù)編碼的字符串

描述方法參數(shù)類型的字符數(shù)組的第一個字符是代表返回值的類型,后面的字符依次代表參數(shù)的類型,因?yàn)镺bjective-C中的函數(shù)會包含兩個隱式參數(shù),也就是方法調(diào)用者和方法名,例如

+(void)method

實(shí)際應(yīng)該是

void method(id self, SEL _cmd)

如果返回值為空,那么函數(shù)的類型編碼的第一個字符是v,如果不為空,則為返回值類型對應(yīng)的編碼,詳細(xì)的可以看下面的編碼對應(yīng)表
因?yàn)榈谝粋€參數(shù)是方法調(diào)用者,它的類型肯定是對象類型,所以類型編碼的第二個字符一定是@

因?yàn)榈诙€參數(shù)是方法名的類型,第三個字符一定是 :

所以這個函數(shù)

void method(id self, SEL _cmd)        

的類型編碼為 "v@:"

那么如果要添加的函數(shù)是一個set函數(shù),類型編碼是怎么樣的呢?

-(void)setA:(NSString *)a

同理,set方法實(shí)際的函數(shù)是這樣的:

void setA(id self, SEL _cmd, id a)  

與上面無參數(shù)的方法相比,只是多了一個參數(shù),

所以類型編碼為"v@:@",代碼為

class_addMethod(self, @selector(setA:), (IMP)setA,"v@:@");

iOS中提供了一個叫做@encode的指令,可以將具體的類型表示成字符串編碼


encoding type

encoding type

由此方法描述的幾個字段我們就講解完了,接下來我們來講解一下方法緩存

4.2方法緩存

在本文第一個圖例,類結(jié)構(gòu)中有一個cache字段

struct objc_class : objc_object {
    Class ISA;
    Class superclass;
    cache_t cache;             //方法緩存 formerly cache pointer and vtable
    class_data_bits_t bits;    //用于獲取具體類的信息 class_rw_t * plus custom rr/alloc flags
}
   

cache_t cache 就是用來緩存方法的,下面我們來看一下cache_t的內(nèi)部結(jié)構(gòu)

objc-runtime-new.h

struct cache_t {
    struct bucket_t *_buckets;  // 散列表
    mask_t _mask;       //散列表的長度-1
    mask_t _occupied;  //已經(jīng)緩存的方法數(shù)量
}


struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    uintptr_t _imp;    //方法實(shí)現(xiàn)地址
    SEL _sel;   //sel  作為key
#else
    SEL _sel;
    uintptr_t _imp;
#endif

cache以方法名為 key方法地址為value 緩存。

4.2.1緩存查找實(shí)現(xiàn)
bucket_t * cache_t::find(SEL s, id receiver)
{
    assert(s != 0);

    bucket_t *b = buckets();
    mask_t m = mask();
    mask_t begin = cache_hash(s, m);
    mask_t i = begin;
    do {
        if (b[i].sel() == 0  ||  b[i].sel() == s) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)s, cls);
}

static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask; //找不到就+1再找 這是解決散列沖突最簡單的一種實(shí)現(xiàn)方式
}

當(dāng)去查找一個方法是否在緩存中時,就會那這個方法名通過一個散列函數(shù)計(jì)算出他在buckets數(shù)組中的位置然后在&mask就能拿到一個函數(shù)的實(shí)現(xiàn)地址,

4.2.2緩存添加實(shí)現(xiàn)
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();

    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;

    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    if (cache_getImp(cls, sel)) return;

    cache_t *cache = getCache(cls);

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1;
    mask_t capacity = cache->capacity();
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand(); // 緩存擴(kuò)容,生成一個新的數(shù)組大小是現(xiàn)有數(shù)組2倍,清空當(dāng)前方法緩存
    }

    // Scan for the first unused slot and insert there.
    // There is guaranteed to be an empty slot because the 
    // minimum size is 4 and we resized at 3/4 full.
    bucket_t *bucket = cache->find(sel, receiver);
    if (bucket->sel() == 0) cache->incrementOccupied();
    bucket->set<Atomic>(sel, imp);
}

void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
    mutex_locker_t lock(cacheUpdateLock);
    cache_fill_nolock(cls, sel, imp, receiver);
#else
    _collecting_in_critical();
    return;
#endif
}

添加緩存首先要查找緩存是否存在,存在則返回,無則添加,當(dāng)緩存滿了的時候就會擴(kuò)容,擴(kuò)容后并不會把現(xiàn)有緩存copy到新數(shù)組中,而是把現(xiàn)有數(shù)組清空。所以每當(dāng)緩存滿了的時候就會,擴(kuò)容,以前調(diào)用的方法都需要重新調(diào)用時才會緩存。

下面我們總結(jié)一下方法調(diào)用的一個整體過程:
1.首先通過isa指針找到類對象然后去類對象的緩存cache中去查找方法,如果查找到該方法則直接調(diào)用

2.如果在類對象中未找到方法,則去類對象的方法列表尋找方法,如果找到方法,則調(diào)用該方法,同時緩存一份到cache中

3.如果在類對象的cache和方法列表中都沒有找到該方法,則通過類對象的superClass指針到父類的類對象的cache中查找,如果找到,則調(diào)用該方法,同時緩存一份到自身的類對象的cache中

4.如果在自身的類對象的cache中,方法列表中,父類的cache中都沒找到,則到父類的方法列表中查找,如果找到,則調(diào)用該方法,同時緩存一 份到父類類對象的cache中,也緩存一份到自己類對象的cache中.

5.如果在父類的方法列表里也找不到該方法,則重復(fù)執(zhí)行4,層層向上查找,直到找到NSObject,如果NSObject都沒有,那就會結(jié)束 報(bào)錯,其實(shí)當(dāng)一個方法找不到時并不會立即報(bào)錯,它會進(jìn)入消息轉(zhuǎn)發(fā)流程,給你三次機(jī)會去處理,這下來我們就講一下這個流程。

五 方法調(diào)用及消息轉(zhuǎn)發(fā)流程

首先我們先看一段代碼:

#include<stdio.h>
#include "Person.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
 int main()
{
    Person *person = [[Person alloc] init];
    [person speak];
}

把它轉(zhuǎn)成c++代碼:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

 int main()
{
    Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("speak"));
}

我們發(fā)現(xiàn)objc_msgSend參與了對象創(chuàng)建以及方法調(diào)用,OC中的方法調(diào)用,其實(shí)都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用。OC的方法調(diào)用就是利用objc_msgSend這種消息機(jī)制,給方法調(diào)用者發(fā)送消息,它有兩個參與內(nèi)容:

1 消息接受者 (receiver):person
2 消息名稱 :init ,speak

objc_msgSend的執(zhí)行流程可以分為3大階段
1 消息發(fā)送
2 動態(tài)方法解析
3 消息轉(zhuǎn)發(fā)
下面我們從源碼來分析objc_msgSend執(zhí)行流程,先看一下大致的執(zhí)行流程,然后我們在分析每個階段的流程

objc-msg-arm64.s
ENTRY _objc_msgSend  // 1 進(jìn)入消息流程
b.le  LNilOrTagged
CacheLookup NORMAL  //查看方法緩存
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup。  //查找方法
__class_lookupMethodAndLoadCache3

objc-runtime-new.mm
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod。  // 2 動態(tài)方法解析
_objc_msgForward_impcache

objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward   //3 進(jìn)入消息轉(zhuǎn)發(fā)
Core Foundation
__forwarding__(不開源)
5.1.1 消息發(fā)送
objc_msgSend 的源碼實(shí)現(xiàn)
ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame

    cmp p0, #0          //1 消息接收者,receiver  nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        // 小于等于零跳轉(zhuǎn)到 LNilOrTagged  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    CacheLookup NORMAL      // 2 查找緩存,具體實(shí)現(xiàn)看 CacheLookup calls imp or objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // 為空跳轉(zhuǎn)到 LReturnZero nil check

    // tagged
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr x16, [x10, x11, LSL #3]
    adrp    x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
    add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
    cmp x10, x16
    b.ne    LGetIsaDone

    // ext tagged
    adrp    x10, _objc_debug_taggedpointer_ext_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
    ubfx    x11, x0, #52, #8
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
    // x0 is already zero
    mov x1, #0
    movi    d0, #0
    movi    d1, #0
    movi    d2, #0
    movi    d3, #0
    ret            //return 退出程序

    END_ENTRY _objc_msgSend

.macro CacheLookup
    // p1 = SEL, p16 = isa
    ldp p10, p11, [x16, #CACHE] // 3 查找方法緩存  p10 = buckets, p11 = occupied|mask
#if !__LP64__
    and w11, w11, 0xffff    // p11 = mask
#endif
    and w12, w1, w11        // x12 = _cmd & mask
    add p12, p10, p12, LSL #(1+PTRSHIFT)
                     // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // 4 緩存查找到就返回函數(shù)地址 call or return imp
    
2:  // not hit: p12 = not-hit bucket  // 沒有查找到緩存
    CheckMiss $0            // 5 沒找到緩存CheckMiss    miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // wrap: p12 = first bucket, w11 = mask
    add p12, p12, w11, UXTW #(1+PTRSHIFT)
                                // p12 = buckets + (mask << 1+PTRSHIFT)

    // Clone scanning loop to miss instead of hang when cache is corrupt.
    // The slow path may detect any corruption and halt later.

    ldp p17, p9, [x12]      // {imp, sel} = *bucket
1:  cmp p9, p1          // if (bucket->sel != _cmd)
    b.ne    2f          //     scan more
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
    CheckMiss $0            // 6 沒有找到則調(diào)用CheckMiss  miss if bucket->sel == 0
    cmp p12, p10        // wrap if bucket == buckets
    b.eq    3f
    ldp p17, p9, [x12, #-BUCKET_SIZE]!  // {imp, sel} = *--bucket
    b   1b          // loop

3:  // double wrap
    JumpMiss $0
    
.endmacro

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached    //7 調(diào)用__objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

STATIC_ENTRY __objc_msgSend_uncached
    UNWIND __objc_msgSend_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup  // 8 查找方法表
    TailCallFunctionPointer x17

    END_ENTRY __objc_msgSend_uncached


    STATIC_ENTRY __objc_msgLookup_uncached
    UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band p16 is the class to search
    
    MethodTableLookup
    ret

    END_ENTRY __objc_msgLookup_uncached


    STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP

.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // receiver and selector already in x0 and x1
    mov x2, x16
    bl  __class_lookupMethodAndLoadCache3  // 9 跳轉(zhuǎn)調(diào)用__class_lookupMethodAndLoadCache3

    // IMP in x0
    mov x17, x0
    
    // restore registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro
10 class_lookupMethodAndLoadCache3 定義
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);//cls 類對象, sel 類名, obj 消息接收者, 
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

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

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (initialize && !cls->isInitialized()) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again

        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }


 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists. 查找類對象的方法列表
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists. 查找父類對象的方法列表
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass); //cls 將父類方法填充到類對像的緩存中
                    goto done;  //找到了跳轉(zhuǎn) done
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel); //查找父類的父類的方法列表
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass); //添加緩存到當(dāng)前類對象
                imp = meth->imp;
                goto done;    //找到了直接跳轉(zhuǎn)done
            }
        }
    }

    // No implementation found. Try method resolver once.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        resolveMethod(cls, sel, inst);   //進(jìn)入消息轉(zhuǎn)發(fā)流程
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;  // 返回函數(shù)地址
}

objc_msgSend由于調(diào)用的頻次非常頻繁,為了提高執(zhí)行效率,所以使用了匯編來實(shí)現(xiàn),它的大致實(shí)現(xiàn)流程是
1.首先通過isa指針找到類對象然后去類對象的緩存cache中去查找方法,如果查找到該方法則直接調(diào)用

2.如果在類對象中未找到方法,則去類對象的方法列表尋找方法,如果找到方法,則調(diào)用該方法,同時緩存一份到cache中

3.如果在類對象的cache和方法列表中都沒有找到該方法,則通過類對象的superClass指針到父類的類對象的cache中查找,如果找到,則調(diào)用該方法,同時緩存一份到自身的類對象的cache中

4.如果在自身的類對象的cache中,方法列表中,父類的cache中都沒找到,則到父類的方法列表中查找,如果找到,則調(diào)用該方法,同時緩存一 份到父類類對象的cache中,也緩存一份到自己類對象的cache中.

5.如果在父類的方法列表里也找不到該方法,則重復(fù)執(zhí)行4,層層向上查找,直到找到NSObject,如果NSObject都沒有,那就會進(jìn)入動態(tài)方法解析。執(zhí)行流程圖如下:


objc_msgSend 消息發(fā)送流程

講完了objc_msgSend 消息發(fā)送流程,接下來我們講下 動態(tài)方法解析流程。

5.1.2 動態(tài)方法解析

先看一下底層實(shí)現(xiàn)源碼:

我們截取了上面lookUpImpOrForward中的一段代碼
 if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;  //重新走查找方法流程
    }
static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! cls->isMetaClass()) { //如果不是元類對象
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst); //調(diào)用resolveInstanceMethod
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(cls, sel, inst); //否則調(diào)用resolveClassMethod
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }
    }
}
static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); //那當(dāng)前類對象調(diào)用resolveInstanceMethod

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
static void resolveClassMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel);//拿當(dāng)前元類對象調(diào)用resolveClassMethod

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

1 前面的消息發(fā)送沒有找到要調(diào)用的方法就會調(diào)用 resolveMethod(cls, sel, inst)
2 resolveMethod(cls, sel, inst)內(nèi)部做了議程判斷
* 如果是元類對象則調(diào)用resolveClassMethod(cls, sel, inst);
*否則調(diào)用:resolveInstanceMethod(cls, sel, inst);
3 resolveClassMethod(cls, sel, inst); 內(nèi)部則是拿當(dāng)前的元類對象調(diào)用resolveClassMethod方法,所以我們要進(jìn)行消息處理則要在類里實(shí)現(xiàn)這個方法

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if(sel == @selector(eat)){
        Method method = class_getInstanceMethod(self, @selector(other));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
        
    }
    return [super resolveInstanceMethod:<#sel#>];
}

4 resolveInstanceMethod(cls, sel, inst) 內(nèi)部這是拿當(dāng)前的類對象調(diào)用resolveInstanceMethod方法同理我們要進(jìn)行消息處理則要在類里實(shí)現(xiàn)這個方法

+ (BOOL)resolveClassMethod:(SEL)sel{
    if(sel == @selector(eat)){
           Method method = class_getInstanceMethod(self, @selector(other));
           class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
           return YES;
           
       }
       return [super resolveClassMethod:<#sel#>];
}

最后看一下它的執(zhí)行流程圖:


消息處理流程

動態(tài)解析過后,會重新走“消息發(fā)送”的流程
“從receiverClass的cache中查找方法”這一步開始執(zhí)行,如果開發(fā)者詳細(xì)處理的兩個方法都沒有實(shí)現(xiàn),會怎么辦呢,那么他就會走接下來的消息轉(zhuǎn)發(fā)流程。

5.1.3 消息轉(zhuǎn)發(fā)

lookUpImpOrForward接近最后兩行代碼

  imp = (IMP)_objc_msgForward_impcache;  //執(zhí)行__objc_msgForward_impcache
  cache_fill(cls, sel, imp, inst);
objc-msg-arm64.s

STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    
    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]  //執(zhí)行__objc_forward_handler
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward
STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward //執(zhí)行__objc_msgForward

    END_ENTRY __objc_msgForward_impcache
//為代碼
int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 調(diào)用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            if (isStret == 1) {
                int ret;
                objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
                return ret;
            }
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 僵尸對象
    const char *className = class_getName(receiverClass);
    const char *zombiePrefix = "_NSZombie_";
    size_t prefixLen = strlen(zombiePrefix); // 0xa
    if (strncmp(className, zombiePrefix, prefixLen) == 0) {
        CFLog(kCFLogLevelError,
              @"*** -[%s %s]: message sent to deallocated instance %p",
              className + prefixLen,
              selName,
              receiver);
        <breakpoint-interrupt>
    }

    // 調(diào)用 methodSignatureForSelector 獲取方法簽名后再調(diào)用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature) {
            BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
            if (signatureIsStret != isStret) {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.",
                      selName,
                      signatureIsStret ? "" : not,
                      isStret ? "" : not);
            }
            if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
                NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

                [receiver forwardInvocation:invocation];

                void *returnValue = NULL;
                [invocation getReturnValue:&value];
                return returnValue;
            } else {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
                      receiver,
                      className);
                return 0;
            }
        }
    }

    SEL *registeredSel = sel_getUid(selName);

    // selector 是否已經(jīng)在 Runtime 注冊過
    if (sel != registeredSel) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
              sel,
              selName,
              registeredSel);
    } // doesNotRecognizeSelector
    else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }
    else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
              receiver,
              className);
    }

    // The point of no return.
    kill(getpid(), 9);
}

最終我們發(fā)現(xiàn)它會調(diào)用兩個方法:
1 forwardingTargetForSelector: 返回yes 執(zhí)行objc_msgSend(返回值, SEL )返回nil這執(zhí)行第二步
2 methodSignatureForSelector //方法簽名如果這個方法有返回值的話這會執(zhí)行下一步
3 (void)forwardInvocation:(NSInvocation *)anInvocation //NSInvocation封裝了一個方法調(diào)用,包括,調(diào)用者,方法名,方法參數(shù),只要能進(jìn)入到這個方法,它里面的實(shí)現(xiàn)可以隨便寫,哪怕打印一句話也行或者任何處理也沒有也行。
消息轉(zhuǎn)發(fā)調(diào)用流程圖如下:


消息轉(zhuǎn)發(fā)調(diào)用流程

1 蘋果的文檔里,講述了這一個消息轉(zhuǎn)發(fā)的出發(fā)點(diǎn),其實(shí)是為了實(shí)現(xiàn)類似C多繼承的功能。我們知道,在C中如果一個類想要具有多個類的功能,是可以直接繼承多個類的。而Objective-C是單繼承,如果想實(shí)現(xiàn)類似的功能,就用消息轉(zhuǎn)發(fā),將消息轉(zhuǎn)發(fā)給有能力處理的類。蘋果是這樣描述他們的思想的:C的多繼承,是加法,在多繼承的同時,其實(shí)也增加了很多不需要的功能,而蘋果通過消息轉(zhuǎn)發(fā),實(shí)現(xiàn)了減法的思想,只留有用的方法,而不去增加過多內(nèi)容。
2 開發(fā)者可以在forwardInvocation:方法中自定義任何邏輯
3 以上方法都有對象方法、類方法2個版本(前面可以是加號+,也可以是減號-)

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
        return [[Person alloc] init];//將eat方法j轉(zhuǎn)交給Person對象執(zhí)行,person要實(shí)現(xiàn)eat方法
    }
    return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    
}


+ (id)forwardingTargetForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
        return [[Person alloc] init];//將eat方法j轉(zhuǎn)交給Person對象執(zhí)行,person要實(shí)現(xiàn)eat方法
    }
    return [super forwardingTargetForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if(aSelector == @selector(eat)) {
         NSMethodSignature *signature = [[[Person alloc] init] methodSignatureForSelector:@selector(eat)];
        return signature;
       }
       return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    anInvocation.target;
    anInvocation.selector;
    [anInvocation getArgument:NULL atIndex:2];// 第三個參數(shù)才是你的方法參數(shù),前兩個是方法的默認(rèn)參數(shù)
    [anInvocation invoke];
}
六 runtime常用api
6.1類
  • 動態(tài)創(chuàng)建一個類(參數(shù):父類,類名,額外的內(nèi)存空間)
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

*注冊一個類(要在類注冊之前添加成員變量)
void objc_registerClassPair(Class cls)

*銷毀一個類
void objc_disposeClassPair(Class cls)

*獲取isa指向的Class
Class object_getClass(id obj)

*設(shè)置isa指向的Class
Class object_setClass(id obj, Class cls)

*判斷一個OC對象是否為Class
BOOL object_isClass(id obj)

*判斷一個Class是否為元類
BOOL class_isMetaClass(Class cls)

*獲取父類
Class class_getSuperclass(Class cls)

6.2成員變量

*獲取一個實(shí)例變量信息
Ivar class_getInstanceVariable(Class cls, const char *name)

*拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

*設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)

*動態(tài)添加成員變量(已經(jīng)注冊的類是不能動態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

*獲取成員變量的相關(guān)信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)

6.3屬性

*獲取一個屬性
objc_property_t class_getProperty(Class cls, const char *name)

*拷貝屬性列表(最后需要調(diào)用free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

*動態(tài)添加屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)

*動態(tài)替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)

*獲取屬性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

6.4方法

*獲得一個實(shí)例方法、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

*方法實(shí)現(xiàn)相關(guān)操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)

*拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

*動態(tài)添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

*動態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

*獲取方法的相關(guān)信息(帶有copy的需要調(diào)用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
*選擇器相關(guān)
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

*用block作為方法實(shí)現(xiàn)
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
到此我們整個runtime基本知識都以經(jīng)講完了,下面我們來看一下runtime在實(shí)際開發(fā)中的應(yīng)用

七 runtime開發(fā)中具體應(yīng)用

runtime的應(yīng)用,主要有幾種:

  • 1 AOP,切面編程,做打點(diǎn)
  • 2 method swizzling,黑魔法做崩潰等的保護(hù)
  • 3 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
  • 4 遍歷類的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動歸檔解檔)
  • 5 交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
    我們下面舉兩個例子來講一下
7.1 Method Swizzling

通過修改一個已存在類的方法, 來實(shí)現(xiàn)方法替換是比較常用的runtime技巧.

如在UIView的load方法中:

+ (void)load {
     Method origin = class_getInstanceMethod([UIView class], @selector(touchesBegan:withEvent:));
     Method custom = class_getInstanceMethod([UIView class], @selector(custom_touchesBegan:withEvent:));
     method_exchangeImplementations(origin, custom);
}

- (void)custom_touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     // TODO
}

這樣, 想要觸發(fā)UIView的touchesBegan:withEvent:方法時, 實(shí)際調(diào)用的卻是自定義的custom_touchesBegan:withEvent:方法.

另外, 關(guān)于runtime method swizzling的一個使用場景, 請參考博客:

iOS — 使用runtime解決3D Touch導(dǎo)致UIImagePicker崩潰的問題

7.2 關(guān)聯(lián)對象

Objective-C中的Category無法向既有的類添加屬性, 因此可以使用runtime的關(guān)聯(lián)對象(associated objects)來實(shí)現(xiàn).

如將一個字符串關(guān)聯(lián)到一個數(shù)組:

static char overviewKey;
NSArray *array = [[NSArray alloc] initWithObjects:@"1", @"2", @"3", nil];
// 為了演示的目的,使用initWithFormat:來確保字符串可以被銷毀
NSString *overview = [[NSString alloc] initWithFormat:@"@", @"first three numbers"];
objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);

這樣, 當(dāng)overview被手動釋放時, 卻不會被銷毀. 因?yàn)殛P(guān)聯(lián)策略指明了數(shù)組array要保有相關(guān)聯(lián)的對象.

而array也釋放時, overview才會被銷毀.

設(shè)置關(guān)聯(lián)對象, 指定被關(guān)聯(lián)對象array, 關(guān)聯(lián)關(guān)鍵字overviewKey, 關(guān)聯(lián)對象overview即可:

objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);

獲取關(guān)聯(lián)對象, 需要傳遞被關(guān)聯(lián)對象array和關(guān)聯(lián)關(guān)鍵字overviewKey:

NSString *associatedObject = (NSString *)objc_getAssociatedObject(array, &overviewKey);

斷開關(guān)聯(lián), 只需要設(shè)置關(guān)聯(lián)對象為nil即可, 關(guān)聯(lián)策略就無所謂了.

objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN);

使用objc_removeAssociatedObjects可斷開所有關(guān)聯(lián), 把對象恢復(fù)至原始狀態(tài).


相關(guān)面試題目
iOS開發(fā)之Runtime常用示例

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,755評論 2 371

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