runtime進(jìn)行曲,objc_msgSend的前世今生(一)

runtime小序曲一文中舉出了runtime的三種應(yīng)用方式:

  • Objective-C源代碼,以objc_msgSend方法舉例。
  • NSObject的方法。
  • Runtime的函數(shù)。

本文將就第一種方式中的objc_msgSend方法展開講解。
學(xué)習(xí)進(jìn)度:

一、objc_msgSend的兩種姿勢

很容易想到,OC中的兩種方法調(diào)用的姿勢:實例方法和類方法。下面我們通過一個簡單樣例的clang處理后的代碼看一下。

// OC代碼
// main.m
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)c;
@end
@implementation A
- (void)b {
    NSLog(@"b");
}
+ (void)c {
    NSLog(@"c");
}
@end
int main(int argc, char * argv[]) {
    A *aObject = [[A alloc] init];
    // 實例方法調(diào)用
    [aObject b];
    // 類方法調(diào)用
    [A c];
}

通過xcrun -sdk iphonesimulator9.3 clang -rewrite-objc main.m將其轉(zhuǎn)換為編譯后的偽代碼,即運行時執(zhí)行的代碼的偽代碼(代碼比較多,只列出我們想要的)。

// A類定義
typedef struct objc_object A;
// objc_getClass方法
struct objc_class *objc_getClass(const char *);
// sel_registerName方法
SEL sel_registerName(const char *str) __attribute__((availability(ios,introduced=2.0)));

// 對應(yīng)上述main函數(shù)
int main(int argc, char * argv[]) {
    A *aObject = ((A *(*)(id, SEL))(void *)objc_msgSend)((id)((A *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("alloc")), sel_registerName("init"));
    // 實例方法調(diào)用
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)aObject, sel_registerName("b"));
    // 類方法調(diào)用
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("c"));
}

將上述實例方法調(diào)用和類方法調(diào)用的強制轉(zhuǎn)換干掉之后看下。

實例方法

objc_msgSend(aObject, sel_registerName("b"));

可知,aObject是一個objc_object結(jié)構(gòu)體,sel_registerName返回值為SEL。所以,這句話的意思大致是:向一個objc_object發(fā)送一個SEL。當(dāng)然,具體怎么發(fā)送的這里先不談。

類方法

objc_msgSend(objc_getClass("A"), sel_registerName("c"));

可知,objc_getClass返回值為objc_class,sel_registerName返回值為SEL。所以,這句話的意思大致是:向一個objc_class發(fā)送一個SEL。當(dāng)然,具體怎么發(fā)送的這里先不談。下文會說。

本節(jié)過后,大概有兩個疑問:
1、objc_object、objc_class、SEL分別是什么東西?
2、objc_msgSend到底如何工作?

二、objc_object和objc_class

上一節(jié)留了兩個問題,這里先回答第一個問題的一部分,即objc_object、objc_class是什么?可以參照runtime源碼。在objc.h和runtime.h里面分別可以找到objc_object和objc_class的實現(xiàn)。

objc_object

// 在objc.h中找到相關(guān)代碼
typedef struct objc_class *Class;
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

動態(tài)類型id其實就是一個objc_object。

可以看出一個類的實例,即一個對象,其實在runtime時刻是一個objc_class結(jié)構(gòu)體,而結(jié)構(gòu)體里面只有一個指向objc_class的指針isa。哎吆,objc_class好像在哪里見過?對,就是上面提到的調(diào)用類方法使用的結(jié)構(gòu)體,下面看一下它的結(jié)構(gòu)。

objc_class

// 在runtime.h中找到相關(guān)代碼
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

objc_class的結(jié)構(gòu)體的東西就比較多了,因為現(xiàn)在討論的是調(diào)用類的方法,所以我們只關(guān)注三個東西isa、super_class和objc_method_list。
isa指向一個objc_class對象,而super_class比較容易理解,即指向這個類的父類。而objc_method_list會在下一節(jié)和SEL一起講解。

本節(jié)并沒有講解一些東西,只是說了下兩個結(jié)構(gòu)體的實現(xiàn),讓大家對isa、super_class有一個概念。本節(jié)過后,大家的疑問應(yīng)該變多了,結(jié)合一中疑問,大概以下幾個:
1、objc_method_list、SEL。
2、isa、super_class有什么用?怎么用?
3、objc_msgSend到底如何工作?

三、objc_method、SEL和IMP

二中第一個疑問是objc_method_list、SEL,而引入objc_method_list是objc_method的列表,所以我們不研究objc_method_list,改為研究objc_method和SEL,而objc_method結(jié)構(gòu)體中還有一個IMP,與本節(jié)內(nèi)容息息相關(guān),所以以objc_method、SEL和IMP為中心講解。

objc_method

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

可知,OC中每一個方法都是如上的結(jié)構(gòu)體,里面的兩個關(guān)鍵字段method_name和method_imp共同為方法的查找和使用服務(wù)。

SEL
從SEL類型的成員為method_name可以知道,SEL大概代表一個方法的名字,可以通過以下方式檢驗。

// OC代碼
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)c;
@end
@implementation A
- (void)b {
}
+ (void)c {
    NSLog(@"%s", @selector(b));
}
@end
int main(int argc, char * argv[]) {
    [A c];
}

// 輸出
2016-12-29 15:23:03.850 block[57622:533311] b

IMP
SEL的作用很容易理解,即想找到想要的objc_method結(jié)構(gòu)體,需要通過SEL來遍歷,那么,IMP是什么呢?一般來說,通過SEL找到想要的objc_method,下一步就是調(diào)用方法的實現(xiàn)了,objc_method結(jié)構(gòu)體中唯一有可能和方法實現(xiàn)相關(guān)的就是IMP了。所以,IMP是一個函數(shù)指針,指向objc_method對應(yīng)方法的實現(xiàn)部分。

objc_method中的method_types指的是方法的返回值和參數(shù)。以返回值開始,依次把參數(shù)拼在后面,比如"i@",即為返回值為int類型,參數(shù)為一個對象,參照對照表。

簡單樣例

// 考慮到只用文字描述有點空洞,這里通過class_addMethod方法的使用做個樣例
//先看下class_addMethod定義,幾個參數(shù)容易理解。cls為需要增加方法的類,后三個參數(shù)對應(yīng)objc_method結(jié)構(gòu)體中的幾個成員。
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

// OC代碼
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
@end
@implementation A
- (void)b {
}
@end
int c(NSString *str) {
    NSLog(@"c");
    return 0;
}
int main(int argc, char * argv[]) {
    class_addMethod([A class], @selector(c:), (IMP)c, "i@");
    A *aObject = [[A alloc] init];
    [aObject performSelector:@selector(c:)];
}

// 輸出,可見完成了方法c的調(diào)用。
2016-12-29 15:51:17.931 block[60624:567679] c

本節(jié)內(nèi)容大致到這,SEL、IMP你應(yīng)該搞懂了,現(xiàn)在只剩下兩個問題:
1、isa、super_class有什么用?怎么用?
2、objc_msgSend到底如何工作?

四、objc_msgSend工作原理和實例方法調(diào)用

1、objc_msgSend工作原理

偽代碼

// 首先看一下objc_msgSend的方法實現(xiàn)的偽代碼
id objc_msgSend(id self, SEL op, ...) {
   if (!self) return nil;
   // 關(guān)鍵代碼(a)
   IMP imp = class_getMethodImplementation(self->isa, SEL op);
   imp(self, op, ...); // 調(diào)用這個函數(shù),偽代碼...
}
// 查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) {
      ... // 執(zhí)行動態(tài)綁定
    }
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; // 這個是用于消息轉(zhuǎn)發(fā)的
    return imp;
}
// 遍歷繼承鏈,查找IMP
IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }
    Class curClass = cls;
    IMP imp = nil;
    do { // 先查緩存,緩存沒有時重建,仍舊沒有則向父類查詢
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass); // 關(guān)鍵代碼(b)
    return imp;
}

分析
首先在Class中的緩存查找imp(沒緩存則初始化緩存),如果沒找到,則向父類的Class查找。如果一直查找到根類仍舊沒有實現(xiàn),則用_objc_msgForward函數(shù)指針代替imp。最后,執(zhí)行這個imp。

2、實例方法調(diào)用

代碼

// 實例方法調(diào)用,參照一
objc_msgSend(aObject, sel_registerName("b"));

// 參照二中實例對象的結(jié)構(gòu)
typedef struct objc_class *Class;
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

// 順便給出objc_class結(jié)構(gòu)
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

分析
結(jié)合關(guān)鍵代碼(a)、關(guān)鍵代碼(b)和上述結(jié)構(gòu),大概可以猜到實例方法的調(diào)用順序:
1、將一個objc_object結(jié)構(gòu)體的isa指針(即其對應(yīng)的objc_class結(jié)構(gòu)體)和一個方法名SEL傳入class_getMethodImplementation。
2、在class_getMethodImplementation方法中,使用lookUpImpOrNil遍歷繼承鏈,若返回nil,則消息轉(zhuǎn)發(fā)(消息轉(zhuǎn)發(fā)下一章在講)。
3、在lookUpImpOrNil遍歷繼承鏈,即先在當(dāng)前objc_class的cache中查找SEL,若沒有,則在methodLists中查找,若存在,則將其放入該objc_class的cache中,然后返回IMP。若不存在,則通過super_class指針找到該class的父class,繼續(xù)該步驟直到NSObject停止(NSObject的super_class指向nil)。
4、執(zhí)行得到的IMP。

調(diào)用順序的第3步也解釋了objc_class 結(jié)構(gòu)體中cache的存在意義。

圖示

實例對象的isa和繼承鏈

3、結(jié)論與問題

本節(jié)介紹了objc_msgSend工作原理、objc_object的isa和objc_class的super_class等的使用方式,但是有一個和本章相關(guān)的成員沒有提到,即objc_class結(jié)構(gòu)體的isa指針。無疑,它是和類方法的調(diào)用直接相關(guān)的。

五、類方法調(diào)用和元類(metaClass)

上面留下的最后一個問題,objc_class結(jié)構(gòu)體的isa指針問題。無疑是和類方法調(diào)用直接相關(guān)的。

1、類方法調(diào)用與實例方法調(diào)用區(qū)別

類方法的調(diào)用和實例方法顯然不用,后者是需要先創(chuàng)建一個實例,而這個實例在堆中有自己對應(yīng)的objc_object結(jié)構(gòu)體,是以這個結(jié)構(gòu)體對應(yīng)的其“私有”的objc_class結(jié)構(gòu)體為基礎(chǔ)進(jìn)行消息傳遞的。
那么,類方法呢?類方法不需要創(chuàng)建實例,每個類都有一個自己對應(yīng)的現(xiàn)成的objc_class結(jié)構(gòu)體。而類方法的調(diào)用也是以這個“公有”的objc_class結(jié)構(gòu)體做操作的。

公有、私有指的是每個對象實例都有自己的objc_class(私有),實例方法的調(diào)用也是通過這個私有的objc_class,而每個類只有一個對應(yīng)的objc_class(公有),任何地方調(diào)用類方法,都是通過這個公有的objc_class。

2、methodLists中的方法

代碼

// OC代碼
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)d;
@end
@implementation A
- (void)b {
    NSLog(@"b");
}
+ (void)d {
    NSLog(@"d");
}
@end
int main(int argc, char * argv[]) {  
    // 在A對應(yīng)的objc_class結(jié)構(gòu)體的繼承鏈中找到實例方法b
    IMP bIMP = class_getMethodImplementation([A class], @selector(b));
    // 執(zhí)行IMP
    bIMP();
}

// 輸出
2016-12-29 18:21:39.810 block[74277:689120] b

分析
可知,可以從A的objc_class的methodLists找到方法b。但是,嘗試將

IMP bIMP = class_getMethodImplementation([A class], @selector(b));

改為

IMP bIMP = class_getMethodImplementation([A class], @selector(d));

執(zhí)行代碼,會crash??梢缘贸鼋Y(jié)論,objc_class的methodLists中只存有實例方法,并沒有類方法。那么類方法在哪里?

類方法的位置

// OC代碼
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface A : NSObject
@property (nonatomic, assign) NSInteger a;
- (void)b;
+ (void)d;
@end
@implementation A
- (void)b {
    NSLog(@"b");
}
+ (void)d {
    NSLog(@"d");
}
@end
int main(int argc, char * argv[]) {
    // 獲取A類對應(yīng)的metaClass
    Class aMeta = objc_getMetaClass(class_getName([A class]));
    // 在metaClass中找類方法d
    IMP dIMP = class_getMethodImplementation(aMeta, @selector(d));
    dIMP();
}

// 輸出
2016-12-29 18:27:19.290 block[74821:695164] d

可知,A的objc_class結(jié)構(gòu)體有一個現(xiàn)成的metaClass,而它存有A類的類方法。這里大概可以想到A類的objc_class結(jié)構(gòu)體里面的isa指針指向這個metaClass。

當(dāng)然,metaClass里面只存了類方法,沒有實例方法。

3、元類(metaClass)

由代碼

Class aMeta = objc_getMetaClass(class_getName([A class]));

可知,元類也是一個objc_class結(jié)構(gòu)體。而相應(yīng)的也有isa和super_class成員。super_class比較好理解,參照四中objc_msgSend實現(xiàn),用于在繼承鏈上方法的查找。而元類的isa均指向NSObject對應(yīng)的元類,這里不多解釋(NSObject對應(yīng)元類的isa指向自己,其super_class指向NSObject,也不解釋了)。

4、類方法調(diào)用流程

// 類方法調(diào)用,參照一
objc_msgSend(objc_getClass("A"), sel_registerName("c"));

// 順便給出objc_class結(jié)構(gòu)
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

可知,objc_getClass("A"),即為獲取A的class,結(jié)合四中objc_msgSend實現(xiàn),將其isa指針指向的metaClass傳入class_getMethodImplementation來進(jìn)行查找,符合我們的預(yù)期。其后的步驟與實例方法相同。

5、類調(diào)用圖示

類的isa和繼承鏈

六、結(jié)論

結(jié)合四和五的圖展示,現(xiàn)在,你看懂這個圖了嗎。(可以發(fā)現(xiàn)本文在四中留下了兩個疑問,即動態(tài)綁定和消息轉(zhuǎn)發(fā),且等下一篇分享)


runtime經(jīng)典圖

七、文獻(xiàn)

1、http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
2、http://blog.csdn.net/reylen/article/details/50440450
3、http://cocoasamurai.blogspot.jp/2010/01/understanding-objective-c-runtime.html
4、http://blog.ibireme.com/2013/11/26/objective-c-messaging/
5、https://github.com/opensource-apple/objc4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,776評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,232評論 0 7
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 771評論 0 2
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 798評論 0 1
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識,它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 825評論 0 4