在Runtime初識(shí)中,我們知曉了Runtime所能夠提供的功能温峭。那么Runtime為什么能夠提供這樣的功能呢猛铅?這就需要從消息分發(fā)機(jī)制說起。為了能夠了解消息分發(fā)凤藏,首先從數(shù)據(jù)結(jié)構(gòu)開始奸忽。
NSObject數(shù)據(jù)結(jié)構(gòu)
除了繼承自NSProxy類以外揖庄,其他的類的基類都是NSObject。首先來看下其NSObject的數(shù)據(jù)結(jié)構(gòu):
NSObject
在去除預(yù)處理指令后的定義:
@interface NSObject<NSObject>{
Class isa OBJC_ISA_AVAILABILITY;
}
可以看出NSObject有一個(gè)Class結(jié)構(gòu)的isa屬性苛萎。這個(gè)屬性很重要,可以將其看成普通的結(jié)構(gòu)體和OC類之間的差異腌歉。凡是繼承自NSObject的類,都會(huì)有isa翘盖。如果沒有isa,那么就不是OC類了(繼承自NSProxy例外)阁危。
Class
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable * _Nullable methodLists OBJC_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
}
可以看出汰瘫,Class也是含有isa指針的,可以將Class看成一個(gè)繼承自NSObject類趴乡。
Class類里面有兩個(gè)指向Class的指針。一個(gè)叫做isa晾捏,另外一個(gè)叫做super_class哀托。這兩者之間又有什么關(guān)系呢?
為了解釋清楚兩者之間的關(guān)系,現(xiàn)在引入元類的概念仓手。
- 每個(gè)繼承自NSObject的實(shí)例俗或,其都會(huì)存在一個(gè)isa指針指向類(Class)。
- 每個(gè)類依然還會(huì)有一個(gè)isa指針辛慰,其指向元類(metaClass)。
- 元類和類具有相同的數(shù)據(jù)結(jié)構(gòu)驰弄,但是具有不同的服務(wù)對(duì)象速客。類(Class)為實(shí)例(instance)提供服務(wù)戚篙,元類(metaClass)為類提供服務(wù)溺职。
更為具體一點(diǎn)岔擂,使用“-”開頭的實(shí)例方法都存在類里面,使用"+"開頭的方法會(huì)放在元類里面塑崖。
@interface UserClass : NSObject
@property (nonatomic) variable;//存放在實(shí)例(instance)中痛倚。
- (void)instanceMethod; //存放在類(Class)中。
+ (void)classMethod; //存放元類(metaClass)中抒蚜。
@end
由此可以看出耘戚,一個(gè)通常意義下的類可以被分解成3個(gè)部分:實(shí)例(instance),實(shí)例方法區(qū)(Class)毕莱,類方法區(qū)(metaClass)颅夺。所以不用被Class這個(gè)名字所迷惑了,實(shí)際上它是存放類的方法和一些其他元數(shù)據(jù)的地方部服,只是我們通常所說類的一個(gè)部分而已拗慨。
理清楚這部分的關(guān)系后,再來看isa和superClass之間的關(guān)系赵抢。一個(gè)是指元類,另一個(gè)是指父類宠叼。兩個(gè)東西是兩個(gè)概念其爵。下面引用一張非常經(jīng)典的圖,用于理順實(shí)例摩渺,類,元類和父類之間的關(guān)系横侦。
Method
前面看完了類的定義,現(xiàn)在來看一下方法的定義辆影。
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
可以看出一個(gè)方法(method)包括3個(gè)部分蛙讥。SEL就是通過@selector()獲取到的數(shù)據(jù)結(jié)構(gòu),可以簡(jiǎn)單看成一個(gè)方法的名稱次慢。method_types就是參數(shù)和返回值的數(shù)據(jù)類型。IMP就是函數(shù)指針了劈愚。
IMP
函數(shù)指針很好理解闻妓,就是C語言的函數(shù)指針。定義為:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
在使用IMP的時(shí)候由缆,可以直接像函數(shù)指針那樣去使用:
//1.獲取函數(shù)指針
IMP funcPoint = class_getMethodImplementation([self class], @selector(xxx));//"xxx"代表具體的方法名均唉,例如可以叫sayHelloWorld。
//2.使用函數(shù)指針
funcPoint(self,@selector(xxx)); //如果報(bào)錯(cuò)舔箭,則需要關(guān)閉Enable Strict Checking of objc_msgSendCalls。
關(guān)閉Xcode中的Enable Strict Checking of objc_msgSendCalls編譯選項(xiàng):
當(dāng)然也可以采用一些其他的手段來解決報(bào)錯(cuò)的問題靴庆,例如定義一個(gè)函數(shù)指針類型怒医,然后將獲得IMP進(jìn)行強(qiáng)制轉(zhuǎn)換。
method_types
method_types是一個(gè)char指針焰薄,是方法的參數(shù)和返回值類型組成的字串。以返回值開始塞茅,依次把參數(shù)拼接在一起。例如“i@”就表示返回值類型為int,參數(shù)類型為id描沟。每個(gè)類型對(duì)照什么樣的參數(shù)鞭光,具體可以參考Objective-C Runtime Programming Guide。
SEL
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
在實(shí)際使用的過程中惰许,感覺selector完全就是承擔(dān)一個(gè)方法名稱的作用。
現(xiàn)在問題來了佩伤,SEL和IMP是什么關(guān)系晦毙?
方法調(diào)用的過程是從SEL尋找到IMP,然后再使用IMP執(zhí)行障斋。所以說SEL的作用僅僅只是用來尋找到IMP,真正進(jìn)行執(zhí)行的是IMP返敬。
那么為什么要SEL寥院?直接使用IMP不好秸谢?
直接使用IMP當(dāng)然可以泉瞻,并且C語言就只使用函數(shù)指針望侈。OC使用SEL其實(shí)就是為了能夠更好的實(shí)現(xiàn)一些語言的動(dòng)態(tài)性坦仍,例如方法交換(method swizzle)管跺。
總結(jié)
- 介紹了元類冷尉,并說明了元類和類的關(guān)系雀哨。
- 介紹了SEL和IMP。并說明了SEL和IMP的關(guān)系怜庸。
參考: