runtime小序曲一文中舉出了runtime的三種應(yīng)用方式:
- Objective-C源代碼,以objc_msgSend方法舉例旺遮。
- NSObject的方法赵讯。
- Runtime的函數(shù)。
本文將就第一種方式中的objc_msgSend方法展開講解耿眉。
學(xué)習(xí)進(jìn)度:
- runtime小序曲边翼,從運(yùn)行時(shí)多態(tài)看這股神秘力量
- runtime進(jìn)行曲,objc_msgSend的前世今生(一)
- runtime進(jìn)行曲鸣剪,objc_msgSend的前世今生(二)
- runtime變奏曲组底,那些藏在runtime中的接口(一)
- runtime變奏曲丈积,那些藏在runtime中的接口(二)
一、objc_msgSend的兩種姿勢(shì)
很容易想到斤寇,OC中的兩種方法調(diào)用的姿勢(shì):實(shí)例方法和類方法桶癣。下面我們通過(guò)一個(gè)簡(jiǎn)單樣例的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];
// 實(shí)例方法調(diào)用
[aObject b];
// 類方法調(diào)用
[A c];
}
通過(guò)xcrun -sdk iphonesimulator9.3 clang -rewrite-objc main.m將其轉(zhuǎn)換為編譯后的偽代碼娘锁,即運(yùn)行時(shí)執(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)));
// 對(duì)應(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"));
// 實(shí)例方法調(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"));
}
將上述實(shí)例方法調(diào)用和類方法調(diào)用的強(qiáng)制轉(zhuǎn)換干掉之后看下莫秆。
實(shí)例方法
objc_msgSend(aObject, sel_registerName("b"));
可知间雀,aObject是一個(gè)objc_object結(jié)構(gòu)體,sel_registerName返回值為SEL镊屎。所以惹挟,這句話的意思大致是:向一個(gè)objc_object發(fā)送一個(gè)SEL。當(dāng)然缝驳,具體怎么發(fā)送的這里先不談连锯。
類方法
objc_msgSend(objc_getClass("A"), sel_registerName("c"));
可知,objc_getClass返回值為objc_class用狱,sel_registerName返回值為SEL运怖。所以,這句話的意思大致是:向一個(gè)objc_class發(fā)送一個(gè)SEL夏伊。當(dāng)然摇展,具體怎么發(fā)送的這里先不談。下文會(huì)說(shuō)溺忧。
本節(jié)過(guò)后咏连,大概有兩個(gè)疑問(wèn):
1、objc_object鲁森、objc_class祟滴、SEL分別是什么東西?
2歌溉、objc_msgSend到底如何工作踱启?
二、objc_object和objc_class
上一節(jié)留了兩個(gè)問(wèn)題研底,這里先回答第一個(gè)問(wèn)題的一部分埠偿,即objc_object、objc_class是什么榜晦?可以參照runtime源碼冠蒋。在objc.h和runtime.h里面分別可以找到objc_object和objc_class的實(shí)現(xiàn)。
objc_object
// 在objc.h中找到相關(guān)代碼
typedef struct objc_class *Class;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
動(dòng)態(tài)類型id其實(shí)就是一個(gè)objc_object乾胶。
可以看出一個(gè)類的實(shí)例抖剿,即一個(gè)對(duì)象朽寞,其實(shí)在runtime時(shí)刻是一個(gè)objc_class結(jié)構(gòu)體,而結(jié)構(gòu)體里面只有一個(gè)指向objc_class的指針isa斩郎。哎吆脑融,objc_class好像在哪里見(jiàn)過(guò)?對(duì)缩宜,就是上面提到的調(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)體的東西就比較多了锻煌,因?yàn)楝F(xiàn)在討論的是調(diào)用類的方法妓布,所以我們只關(guān)注三個(gè)東西isa、super_class和objc_method_list宋梧。
isa指向一個(gè)objc_class對(duì)象匣沼,而super_class比較容易理解,即指向這個(gè)類的父類捂龄。而objc_method_list會(huì)在下一節(jié)和SEL一起講解释涛。
本節(jié)并沒(méi)有講解一些東西,只是說(shuō)了下兩個(gè)結(jié)構(gòu)體的實(shí)現(xiàn)倦沧,讓大家對(duì)isa唇撬、super_class有一個(gè)概念。本節(jié)過(guò)后刀脏,大家的疑問(wèn)應(yīng)該變多了局荚,結(jié)合一中疑問(wèn)超凳,大概以下幾個(gè):
1愈污、objc_method_list、SEL轮傍。
2暂雹、isa、super_class有什么用创夜?怎么用杭跪?
3、objc_msgSend到底如何工作驰吓?
三涧尿、objc_method、SEL和IMP
二中第一個(gè)疑問(wèn)是objc_method_list檬贰、SEL姑廉,而引入objc_method_list是objc_method的列表,所以我們不研究objc_method_list翁涤,改為研究objc_method和SEL桥言,而objc_method結(jié)構(gòu)體中還有一個(gè)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中每一個(gè)方法都是如上的結(jié)構(gòu)體扔涧,里面的兩個(gè)關(guān)鍵字段method_name和method_imp共同為方法的查找和使用服務(wù)园担。
SEL
從SEL類型的成員為method_name可以知道,SEL大概代表一個(gè)方法的名字扰柠,可以通過(guò)以下方式檢驗(yàn)粉铐。
// 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)體卤档,需要通過(guò)SEL來(lái)遍歷蝙泼,那么,IMP是什么呢劝枣?一般來(lái)說(shuō)汤踏,通過(guò)SEL找到想要的objc_method,下一步就是調(diào)用方法的實(shí)現(xiàn)了舔腾,objc_method結(jié)構(gòu)體中唯一有可能和方法實(shí)現(xiàn)相關(guān)的就是IMP了溪胶。所以,IMP是一個(gè)函數(shù)指針稳诚,指向objc_method對(duì)應(yīng)方法的實(shí)現(xiàn)部分哗脖。
objc_method中的method_types指的是方法的返回值和參數(shù)。以返回值開始扳还,依次把參數(shù)拼在后面才避,比如"i@",即為返回值為int類型氨距,參數(shù)為一個(gè)對(duì)象桑逝,參照對(duì)照表。
簡(jiǎn)單樣例
// 考慮到只用文字描述有點(diǎn)空洞俏让,這里通過(guò)class_addMethod方法的使用做個(gè)樣例
//先看下class_addMethod定義楞遏,幾個(gè)參數(shù)容易理解。cls為需要增加方法的類首昔,后三個(gè)參數(shù)對(duì)應(yīng)objc_method結(jié)構(gòu)體中的幾個(gè)成員寡喝。
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:)];
}
// 輸出,可見(jiàn)完成了方法c的調(diào)用勒奇。
2016-12-29 15:51:17.931 block[60624:567679] c
本節(jié)內(nèi)容大致到這预鬓,SEL、IMP你應(yīng)該搞懂了撬陵,現(xiàn)在只剩下兩個(gè)問(wèn)題:
1珊皿、isa网缝、super_class有什么用?怎么用蟋定?
2粉臊、objc_msgSend到底如何工作?
四驶兜、objc_msgSend工作原理和實(shí)例方法調(diào)用
1扼仲、objc_msgSend工作原理
偽代碼
// 首先看一下objc_msgSend的方法實(shí)現(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)用這個(gè)函數(shù),偽代碼...
}
// 查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) {
... // 執(zhí)行動(dòng)態(tài)綁定
}
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward; // 這個(gè)是用于消息轉(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 { // 先查緩存,緩存沒(méi)有時(shí)重建,仍舊沒(méi)有則向父類查詢
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(沒(méi)緩存則初始化緩存)屠凶,如果沒(méi)找到,則向父類的Class查找肆资。如果一直查找到根類仍舊沒(méi)有實(shí)現(xiàn)矗愧,則用_objc_msgForward函數(shù)指針代替imp。最后郑原,執(zhí)行這個(gè)imp唉韭。
2、實(shí)例方法調(diào)用
代碼
// 實(shí)例方法調(diào)用犯犁,參照一
objc_msgSend(aObject, sel_registerName("b"));
// 參照二中實(shí)例對(duì)象的結(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),大概可以猜到實(shí)例方法的調(diào)用順序:
1酸役、將一個(gè)objc_object結(jié)構(gòu)體的isa指針(即其對(duì)應(yīng)的objc_class結(jié)構(gòu)體)和一個(gè)方法名SEL傳入class_getMethodImplementation住诸。
2、在class_getMethodImplementation方法中涣澡,使用lookUpImpOrNil遍歷繼承鏈贱呐,若返回nil,則消息轉(zhuǎn)發(fā)(消息轉(zhuǎn)發(fā)下一章在講)暑塑。
3吼句、在lookUpImpOrNil遍歷繼承鏈锅必,即先在當(dāng)前objc_class的cache中查找SEL事格,若沒(méi)有,則在methodLists中查找搞隐,若存在驹愚,則將其放入該objc_class的cache中,然后返回IMP劣纲。若不存在逢捺,則通過(guò)super_class指針找到該class的父class,繼續(xù)該步驟直到NSObject停止(NSObject的super_class指向nil)癞季。
4劫瞳、執(zhí)行得到的IMP倘潜。
調(diào)用順序的第3步也解釋了objc_class 結(jié)構(gòu)體中cache的存在意義。
圖示
3志于、結(jié)論與問(wèn)題
本節(jié)介紹了objc_msgSend工作原理涮因、objc_object的isa和objc_class的super_class等的使用方式,但是有一個(gè)和本章相關(guān)的成員沒(méi)有提到伺绽,即objc_class結(jié)構(gòu)體的isa指針养泡。無(wú)疑,它是和類方法的調(diào)用直接相關(guān)的奈应。
五澜掩、類方法調(diào)用和元類(metaClass)
上面留下的最后一個(gè)問(wèn)題,objc_class結(jié)構(gòu)體的isa指針問(wèn)題杖挣。無(wú)疑是和類方法調(diào)用直接相關(guān)的肩榕。
1、類方法調(diào)用與實(shí)例方法調(diào)用區(qū)別
類方法的調(diào)用和實(shí)例方法顯然不用惩妇,后者是需要先創(chuàng)建一個(gè)實(shí)例点把,而這個(gè)實(shí)例在堆中有自己對(duì)應(yīng)的objc_object結(jié)構(gòu)體,是以這個(gè)結(jié)構(gòu)體對(duì)應(yīng)的其“私有”的objc_class結(jié)構(gòu)體為基礎(chǔ)進(jìn)行消息傳遞的屿附。
那么郎逃,類方法呢?類方法不需要?jiǎng)?chuàng)建實(shí)例挺份,每個(gè)類都有一個(gè)自己對(duì)應(yīng)的現(xiàn)成的objc_class結(jié)構(gòu)體褒翰。而類方法的調(diào)用也是以這個(gè)“公有”的objc_class結(jié)構(gòu)體做操作的。
公有匀泊、私有指的是每個(gè)對(duì)象實(shí)例都有自己的objc_class(私有)优训,實(shí)例方法的調(diào)用也是通過(guò)這個(gè)私有的objc_class,而每個(gè)類只有一個(gè)對(duì)應(yīng)的objc_class(公有)各聘,任何地方調(diào)用類方法揣非,都是通過(guò)這個(gè)公有的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對(duì)應(yīng)的objc_class結(jié)構(gòu)體的繼承鏈中找到實(shí)例方法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í)行代碼搞监,會(huì)crash×螅可以得出結(jié)論琐驴,objc_class的methodLists中只存有實(shí)例方法,并沒(méi)有類方法。那么類方法在哪里绝淡?
類方法的位置
// 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類對(duì)應(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)體有一個(gè)現(xiàn)成的metaClass,而它存有A類的類方法牢酵。這里大概可以想到A類的objc_class結(jié)構(gòu)體里面的isa指針指向這個(gè)metaClass荐类。
當(dāng)然,metaClass里面只存了類方法茁帽,沒(méi)有實(shí)例方法玉罐。
3、元類(metaClass)
由代碼
Class aMeta = objc_getMetaClass(class_getName([A class]));
可知潘拨,元類也是一個(gè)objc_class結(jié)構(gòu)體吊输。而相應(yīng)的也有isa和super_class成員。super_class比較好理解铁追,參照四中objc_msgSend實(shí)現(xiàn)饶唤,用于在繼承鏈上方法的查找咪啡。而元類的isa均指向NSObject對(duì)應(yīng)的元類,這里不多解釋(NSObject對(duì)應(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實(shí)現(xiàn),將其isa指針指向的metaClass傳入class_getMethodImplementation來(lái)進(jìn)行查找屿岂,符合我們的預(yù)期践宴。其后的步驟與實(shí)例方法相同。
5爷怀、類調(diào)用圖示
六阻肩、結(jié)論
結(jié)合四和五的圖展示,現(xiàn)在运授,你看懂這個(gè)圖了嗎烤惊。(可以發(fā)現(xiàn)本文在四中留下了兩個(gè)疑問(wèn),即動(dòng)態(tài)綁定和消息轉(zhuǎn)發(fā)徒坡,且等下一篇分享)
七撕氧、文獻(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