runtime小序曲一文中舉出了runtime的三種應用方式:
- Objective-C源代碼,以objc_msgSend方法舉例。
- NSObject的方法。
- Runtime的函數。
本文將就第一種方式中的objc_msgSend方法展開講解。
學習進度:
- runtime小序曲,從運行時多態看這股神秘力量
- runtime進行曲,objc_msgSend的前世今生(一)
- runtime進行曲,objc_msgSend的前世今生(二)
- runtime變奏曲,那些藏在runtime中的接口(一)
- runtime變奏曲,那些藏在runtime中的接口(二)
一、objc_msgSend的兩種姿勢
很容易想到,OC中的兩種方法調用的姿勢:實例方法和類方法。下面我們通過一個簡單樣例的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];
// 實例方法調用
[aObject b];
// 類方法調用
[A c];
}
通過xcrun -sdk iphonesimulator9.3 clang -rewrite-objc main.m將其轉換為編譯后的偽代碼,即運行時執行的代碼的偽代碼(代碼比較多,只列出我們想要的)。
// 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)));
// 對應上述main函數
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"));
// 實例方法調用
((void (*)(id, SEL))(void *)objc_msgSend)((id)aObject, sel_registerName("b"));
// 類方法調用
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("A"), sel_registerName("c"));
}
將上述實例方法調用和類方法調用的強制轉換干掉之后看下。
實例方法
objc_msgSend(aObject, sel_registerName("b"));
可知,aObject是一個objc_object結構體,sel_registerName返回值為SEL。所以,這句話的意思大致是:向一個objc_object發送一個SEL。當然,具體怎么發送的這里先不談。
類方法
objc_msgSend(objc_getClass("A"), sel_registerName("c"));
可知,objc_getClass返回值為objc_class,sel_registerName返回值為SEL。所以,這句話的意思大致是:向一個objc_class發送一個SEL。當然,具體怎么發送的這里先不談。下文會說。
本節過后,大概有兩個疑問:
1、objc_object、objc_class、SEL分別是什么東西?
2、objc_msgSend到底如何工作?
二、objc_object和objc_class
上一節留了兩個問題,這里先回答第一個問題的一部分,即objc_object、objc_class是什么?可以參照runtime源碼。在objc.h和runtime.h里面分別可以找到objc_object和objc_class的實現。
objc_object
// 在objc.h中找到相關代碼
typedef struct objc_class *Class;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
動態類型id其實就是一個objc_object。
可以看出一個類的實例,即一個對象,其實在runtime時刻是一個objc_class結構體,而結構體里面只有一個指向objc_class的指針isa。哎吆,objc_class好像在哪里見過?對,就是上面提到的調用類方法使用的結構體,下面看一下它的結構。
objc_class
// 在runtime.h中找到相關代碼
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的結構體的東西就比較多了,因為現在討論的是調用類的方法,所以我們只關注三個東西isa、super_class和objc_method_list。
isa指向一個objc_class對象,而super_class比較容易理解,即指向這個類的父類。而objc_method_list會在下一節和SEL一起講解。
本節并沒有講解一些東西,只是說了下兩個結構體的實現,讓大家對isa、super_class有一個概念。本節過后,大家的疑問應該變多了,結合一中疑問,大概以下幾個:
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結構體中還有一個IMP,與本節內容息息相關,所以以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中每一個方法都是如上的結構體,里面的兩個關鍵字段method_name和method_imp共同為方法的查找和使用服務。
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結構體,需要通過SEL來遍歷,那么,IMP是什么呢?一般來說,通過SEL找到想要的objc_method,下一步就是調用方法的實現了,objc_method結構體中唯一有可能和方法實現相關的就是IMP了。所以,IMP是一個函數指針,指向objc_method對應方法的實現部分。
objc_method中的method_types指的是方法的返回值和參數。以返回值開始,依次把參數拼在后面,比如"i@",即為返回值為int類型,參數為一個對象,參照對照表。
簡單樣例
// 考慮到只用文字描述有點空洞,這里通過class_addMethod方法的使用做個樣例
//先看下class_addMethod定義,幾個參數容易理解。cls為需要增加方法的類,后三個參數對應objc_method結構體中的幾個成員。
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的調用。
2016-12-29 15:51:17.931 block[60624:567679] c
本節內容大致到這,SEL、IMP你應該搞懂了,現在只剩下兩個問題:
1、isa、super_class有什么用?怎么用?
2、objc_msgSend到底如何工作?
四、objc_msgSend工作原理和實例方法調用
1、objc_msgSend工作原理
偽代碼
// 首先看一下objc_msgSend的方法實現的偽代碼
id objc_msgSend(id self, SEL op, ...) {
if (!self) return nil;
// 關鍵代碼(a)
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, ...); // 調用這個函數,偽代碼...
}
// 查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) {
... // 執行動態綁定
}
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward; // 這個是用于消息轉發的
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); // 關鍵代碼(b)
return imp;
}
分析
首先在Class中的緩存查找imp(沒緩存則初始化緩存),如果沒找到,則向父類的Class查找。如果一直查找到根類仍舊沒有實現,則用_objc_msgForward函數指針代替imp。最后,執行這個imp。
2、實例方法調用
代碼
// 實例方法調用,參照一
objc_msgSend(aObject, sel_registerName("b"));
// 參照二中實例對象的結構
typedef struct objc_class *Class;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
// 順便給出objc_class結構
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;
分析
結合關鍵代碼(a)、關鍵代碼(b)和上述結構,大概可以猜到實例方法的調用順序:
1、將一個objc_object結構體的isa指針(即其對應的objc_class結構體)和一個方法名SEL傳入class_getMethodImplementation。
2、在class_getMethodImplementation方法中,使用lookUpImpOrNil遍歷繼承鏈,若返回nil,則消息轉發(消息轉發下一章在講)。
3、在lookUpImpOrNil遍歷繼承鏈,即先在當前objc_class的cache中查找SEL,若沒有,則在methodLists中查找,若存在,則將其放入該objc_class的cache中,然后返回IMP。若不存在,則通過super_class指針找到該class的父class,繼續該步驟直到NSObject停止(NSObject的super_class指向nil)。
4、執行得到的IMP。
調用順序的第3步也解釋了objc_class 結構體中cache的存在意義。
圖示
3、結論與問題
本節介紹了objc_msgSend工作原理、objc_object的isa和objc_class的super_class等的使用方式,但是有一個和本章相關的成員沒有提到,即objc_class結構體的isa指針。無疑,它是和類方法的調用直接相關的。
五、類方法調用和元類(metaClass)
上面留下的最后一個問題,objc_class結構體的isa指針問題。無疑是和類方法調用直接相關的。
1、類方法調用與實例方法調用區別
類方法的調用和實例方法顯然不用,后者是需要先創建一個實例,而這個實例在堆中有自己對應的objc_object結構體,是以這個結構體對應的其“私有”的objc_class結構體為基礎進行消息傳遞的。
那么,類方法呢?類方法不需要創建實例,每個類都有一個自己對應的現成的objc_class結構體。而類方法的調用也是以這個“公有”的objc_class結構體做操作的。
公有、私有指的是每個對象實例都有自己的objc_class(私有),實例方法的調用也是通過這個私有的objc_class,而每個類只有一個對應的objc_class(公有),任何地方調用類方法,都是通過這個公有的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對應的objc_class結構體的繼承鏈中找到實例方法b
IMP bIMP = class_getMethodImplementation([A class], @selector(b));
// 執行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));
執行代碼,會crash。可以得出結論,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類對應的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結構體有一個現成的metaClass,而它存有A類的類方法。這里大概可以想到A類的objc_class結構體里面的isa指針指向這個metaClass。
當然,metaClass里面只存了類方法,沒有實例方法。
3、元類(metaClass)
由代碼
Class aMeta = objc_getMetaClass(class_getName([A class]));
可知,元類也是一個objc_class結構體。而相應的也有isa和super_class成員。super_class比較好理解,參照四中objc_msgSend實現,用于在繼承鏈上方法的查找。而元類的isa均指向NSObject對應的元類,這里不多解釋(NSObject對應元類的isa指向自己,其super_class指向NSObject,也不解釋了)。
4、類方法調用流程
// 類方法調用,參照一
objc_msgSend(objc_getClass("A"), sel_registerName("c"));
// 順便給出objc_class結構
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,結合四中objc_msgSend實現,將其isa指針指向的metaClass傳入class_getMethodImplementation來進行查找,符合我們的預期。其后的步驟與實例方法相同。
5、類調用圖示
六、結論
結合四和五的圖展示,現在,你看懂這個圖了嗎。(可以發現本文在四中留下了兩個疑問,即動態綁定和消息轉發,且等下一篇分享)
七、文獻
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