我們常常會(huì)聽(tīng)說(shuō) Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Objective-C 把很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)做的事情放到了運(yùn)行時(shí)去處理,它在運(yùn)行時(shí)實(shí)現(xiàn)了對(duì)類(lèi)、方法、成員變量、屬性等信息的管理機(jī)制,這一套運(yùn)行時(shí)機(jī)制為我們開(kāi)發(fā)提供了極大的靈活性,比如:
在運(yùn)行時(shí)創(chuàng)建或修改一個(gè)類(lèi)。
在運(yùn)行時(shí)修改成員變量、屬性。
在運(yùn)行時(shí)進(jìn)行消息分發(fā)和方法綁定。
與之對(duì)應(yīng)的實(shí)現(xiàn)就是 Objective-C 短小精悍的 Runtime。對(duì)于蘋(píng)果維護(hù)的 Objective-C 的 Runtime 源碼,你可以在這里看到:Objective-C 源碼。
運(yùn)行時(shí)的類(lèi)與對(duì)象
相關(guān)函數(shù)
Objective-C 的 Runtime 為我們提供了很多運(yùn)行時(shí)狀態(tài)下跟類(lèi)與對(duì)象相關(guān)的函數(shù),比如:
const char *class_getName(Class cls),獲取指定類(lèi)的類(lèi)名。
BOOL class_isMetaClass(Class cls),判斷指定類(lèi)是否是一個(gè)元類(lèi)。
Class class_getSuperclass(Class cls),獲取指定類(lèi)的父類(lèi)。
Class class_setSuperclass(Class cls, Class newSuper),設(shè)定指定類(lèi)的父類(lèi)。
int class_getVersion(Class cls),獲取指定類(lèi)的版本信息。
void class_setVersion(Class cls, int version),設(shè)定指定類(lèi)的版本信息。
size_t class_getInstanceSize(Class cls),獲取實(shí)例大小。
Ivar class_getInstanceVariable(Class cls, const char *name),獲取指定名字的實(shí)例變量。
Ivar class_getClassVariable(Class cls, const char *name),獲取指定名字的類(lèi)變量。
Ivar *class_copyIvarList(Class cls, unsigned int *outCount),獲取類(lèi)的成員變量列表的拷貝。調(diào)用后需要自己 free()。
Method class_getInstanceMethod(Class cls, SEL name),獲取指定名字的實(shí)例方法。
Method class_getClassMethod(Class cls, SEL name),獲取指定名字的類(lèi)方法。
IMP class_getMethodImplementation(Class cls, SEL name),獲取指定名字的方法實(shí)現(xiàn)。
BOOL class_respondsToSelector(Class cls, SEL sel),類(lèi)是否響應(yīng)指定的方法。
Method *class_copyMethodList(Class cls, unsigned int *outCount),獲取方法列表的拷貝。調(diào)用后需要自己 free()。
BOOL class_conformsToProtocol(Class cls, Protocol *protocol),類(lèi)是否遵循指定的協(xié)議。
Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount),獲取協(xié)議列表的拷貝。調(diào)用后需要自己 free()。
objc_property_t class_getProperty(Class cls, const char *name),獲取指定名字的屬性。
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount),獲取類(lèi)的屬性列表。調(diào)用后需要自己 free()。
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types),為類(lèi)添加方法。
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types),替代類(lèi)的方法。
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types),給指定的類(lèi)添加成員變量。這個(gè)函數(shù)只能在 objc_allocateClassPair() 和 objc_registerClassPair() 之間調(diào)用,并且不能為一個(gè)已經(jīng)存在的類(lèi)添加成員變量。
BOOL class_addProtocol(Class cls, Protocol *protocol),為類(lèi)添加協(xié)議。
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount),為類(lèi)添加屬性。
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount),替代類(lèi)的屬性。
id class_createInstance(Class cls, size_t extraBytes),創(chuàng)建指定類(lèi)的實(shí)例。
id objc_constructInstance(Class cls, void *bytes),在指定的位置創(chuàng)建類(lèi)的實(shí)例。
void *objc_destructInstance(id obj),銷(xiāo)毀實(shí)例。
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes),創(chuàng)建類(lèi)和元類(lèi)。
void objc_registerClassPair(Class cls),注冊(cè)類(lèi)到 Runtime。
void objc_disposeClassPair(Class cls),銷(xiāo)毀類(lèi)和對(duì)應(yīng)的元類(lèi)。
上面羅列的函數(shù)只是一部分,在使用這些函數(shù)時(shí),有時(shí)候需要注意一些細(xì)節(jié)信息和使用規(guī)范,具體可以查閱Objective-C Runtime Reference。
類(lèi)的數(shù)據(jù)結(jié)構(gòu)
上面的函數(shù)非常豐富,我們可以看出這些函數(shù)為我們提供了在運(yùn)行時(shí)改變一個(gè)類(lèi)的結(jié)構(gòu)、屬性、方法、協(xié)議等信息的能力。那這些函數(shù)背后所操作的數(shù)據(jù)結(jié)構(gòu)是什么樣的呢?這個(gè)其實(shí)可以在objc/runtime.h的源碼中查到:
structobjc_class {? ? Class isa OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class super_class OBJC2_UNAVAILABLE;// 父類(lèi)。constchar*name OBJC2_UNAVAILABLE;// 類(lèi)名。longversion OBJC2_UNAVAILABLE;// 類(lèi)的版本信息。longinfo OBJC2_UNAVAILABLE;// 類(lèi)信息,供運(yùn)行時(shí)使用的一些位標(biāo)識(shí)。longinstance_size OBJC2_UNAVAILABLE;// 類(lèi)的實(shí)例變量大小。structobjc_ivar_list *ivars OBJC2_UNAVAILABLE;// 類(lèi)的成員變量列表。structobjc_method_list **methodLists OBJC2_UNAVAILABLE;// 方法定義列表。structobjc_cache *cache OBJC2_UNAVAILABLE;// 方法緩存。structobjc_protocol_list *protocols OBJC2_UNAVAILABLE;// 協(xié)議列表。#endif} OBJC2_UNAVAILABLE;
這個(gè)結(jié)構(gòu)其實(shí)就是類(lèi)的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),在 Objective-C 中類(lèi)是由Class表示,而 Class 實(shí)際上是一個(gè)指向struct objc_class的指針。
typedefstructobjc_class *Class;
在這個(gè)類(lèi)的數(shù)據(jù)結(jié)構(gòu)中,有幾個(gè)字段需要再解釋一下:
isa,在大多面向?qū)ο蟮恼Z(yǔ)言中,都有**類(lèi)**和**對(duì)象**的概念,其中,對(duì)象是類(lèi)的實(shí)例,是通過(guò)類(lèi)定義的結(jié)構(gòu)生成出來(lái)的。而在 Objective-C 中,類(lèi)本身也是一個(gè)對(duì)象,類(lèi)作為對(duì)象時(shí)的 isa 指針指向的是元類(lèi)(Meta Class),這個(gè)我們后面再說(shuō)。
super_class,指向該類(lèi)的父類(lèi),如果該類(lèi)已經(jīng)是根類(lèi)(NSObject 或 NSProxy),則 super_class 為 NULL。
cache,用于緩存最近使用的方法。一個(gè)對(duì)象可響應(yīng)的方法列表中通常只有一部分是經(jīng)常被調(diào)用的,cache 則是用來(lái)緩存最常調(diào)用的方法,從而避免每次方法調(diào)用時(shí)都去查找對(duì)象的整個(gè)方法列表。并且,在一些結(jié)構(gòu)較為復(fù)雜的類(lèi)關(guān)系中,一個(gè)對(duì)象的響應(yīng)方法可能來(lái)自于它繼承的類(lèi)結(jié)構(gòu)中,那么查找相應(yīng)的響應(yīng)方法時(shí)就會(huì)比較耗時(shí),通過(guò) cache 緩存也能降低查找時(shí)間。
version,根據(jù)這個(gè)字段可以獲得類(lèi)的版本信息,在對(duì)象的序列化中可以通過(guò)類(lèi)的版本信息來(lái)標(biāo)識(shí)出不同版本的類(lèi)定義中實(shí)例變量布局的改變。
元類(lèi)(Meta Class)
上面講到一個(gè)類(lèi)也是一個(gè)對(duì)象,那么它必然也是某一種類(lèi)的實(shí)例,這種類(lèi)就是:元類(lèi)(Meta Class)。就如類(lèi)是對(duì)應(yīng)的實(shí)例的描述一樣,元類(lèi)則是類(lèi)作為對(duì)象時(shí)的描述。元類(lèi)的方法列表對(duì)應(yīng)的則是類(lèi)方法(Class Method)列表,這正是類(lèi)作為一個(gè)對(duì)象時(shí)所需要的。當(dāng)我們像[NSObject alloc]這樣給一個(gè)類(lèi)發(fā)送消息時(shí),Runtime 就會(huì)去對(duì)應(yīng)的元類(lèi)查找其類(lèi)方法列表,并匹配調(diào)用。
Since a class is an object, it must be an instance of some other class: a metaclass. The metaclass is the description of the class object, just like the class is the description of ordinary instances. Class methods are described by the metaclass on behalf of the class object, just like instance methods are described by the class on behalf of the instance objects.
那在接著往下探究:元類(lèi)又是誰(shuí)的實(shí)例呢?它的 isa 又指向誰(shuí)呢?答案如下圖所示。
元類(lèi)的 isa 都指向根元類(lèi)(Root Meta Class),也就是說(shuō)元類(lèi)都是根元類(lèi)(Root Meta Class)的實(shí)例。而根元類(lèi)(Root Meta Class)的 isa 則指向自己,這樣就不會(huì)無(wú)休止的鏈下去了。
在圖中還能看到類(lèi)的繼承關(guān)系以及對(duì)應(yīng)的元類(lèi)的繼承關(guān)系,已經(jīng)比較清晰了,不再詳述。
類(lèi)的實(shí)例的數(shù)據(jù)結(jié)構(gòu)
在 Objective-C 中類(lèi)的實(shí)例的數(shù)據(jù)結(jié)構(gòu)是定義在struct objc_object中(objc/objc.h):
// Represents aninstanceof a class.struct objc_object {? ? Class isa? OBJC_ISA_AVAILABILITY;};
可以看到,這個(gè)結(jié)構(gòu)體只有一個(gè)字段,即指向該實(shí)例所屬類(lèi)的 isa 指針。這個(gè)跟上面講的類(lèi)的數(shù)據(jù)結(jié)構(gòu)中的 isa 略有不同:類(lèi)的 isa 指向?qū)?yīng)的元類(lèi)(Meta Class),實(shí)例的 isa 則是指向?qū)?yīng)的類(lèi)(Class),而這個(gè) Class 里就如上所講的包含了這個(gè)實(shí)例所屬類(lèi)的各種信息:父類(lèi)、類(lèi)名、方法列表等等。
在我們向一個(gè)類(lèi)的實(shí)例發(fā)送消息時(shí),Runtime 會(huì)根據(jù)實(shí)例對(duì)象的 isa 指針找到這個(gè)實(shí)例對(duì)象所屬的類(lèi),接著再在這個(gè)類(lèi)的方法列表和其父類(lèi)的方法列表中查找與消息對(duì)應(yīng)的 selector 指向的方法,然后執(zhí)行它。
當(dāng)創(chuàng)建某一個(gè)類(lèi)的實(shí)例時(shí),分配的內(nèi)存中會(huì)包含一個(gè) objc_object 數(shù)據(jù)結(jié)構(gòu),然后是類(lèi)的實(shí)例變量的數(shù)據(jù)。
我們常見(jiàn)的 id 是一個(gè) struct objc_object 類(lèi)型的指針。id 類(lèi)型的對(duì)象可以轉(zhuǎn)換為任何一種類(lèi)型的對(duì)象,它的作用有點(diǎn)類(lèi)似 C 語(yǔ)言中的 void * 指針類(lèi)型。
// A pointer to aninstanceof a class.typedef struct objc_object *id;
運(yùn)行時(shí)操作類(lèi)與對(duì)象的代碼示例
實(shí)例、類(lèi)、父類(lèi)、元類(lèi)關(guān)系結(jié)構(gòu)的示例代碼
首先我創(chuàng)建了繼承關(guān)系為SubClass -> SuperClass -> NSObject的幾個(gè)類(lèi),下面就用 Runtime 提供的運(yùn)行時(shí)方法來(lái)打印一下相關(guān)信息:
#import #import"SuperClass.h"#import"SubClass.h"-(void)aboutClass {// Use "object_getClass()" to get "isa".SubClass *sub = [[SubClassalloc] init];NSLog(@"%@, %@", object_getClass(sub), class_getSuperclass(object_getClass(sub))); // Print: SubClass, SuperClassClasscls= objc_getMetaClass("SubClass");if(class_isMetaClass(cls)) {? ? ? ? NSLog(@"YES, %@, %@, %@",cls, class_getSuperclass(cls), object_getClass(cls)); // Print: YES, SubClass, SuperClass, NSObject}else{? ? ? ? NSLog(@"NO");}? ? SuperClass *sup = [[SuperClassalloc] init];NSLog(@"%@, %@", object_getClass(sup), class_getSuperclass(object_getClass(sup))); // Print: SuperClass, NSObjectcls= objc_getMetaClass("SuperClass");if(class_isMetaClass(cls)) {? ? ? ? NSLog(@"YES, %@, %@, %@",cls, class_getSuperclass(cls), object_getClass(cls)); // Print: YES, SuperClass, NSObject, NSObject}else{? ? ? ? NSLog(@"NO");}cls= objc_getMetaClass("UIView");if(class_isMetaClass(cls)) {? ? ? ? NSLog(@"YES, %@, %@, %@",cls, class_getSuperclass(cls), object_getClass(cls)); // Print: YES, UIView, UIResponder, NSObject}else{? ? ? ? NSLog(@"NO");}cls= objc_getMetaClass("NSObject");if(class_isMetaClass(cls)) {? ? ? ? NSLog(@"YES, %@, %@, %@",cls, class_getSuperclass(cls), object_getClass(cls)); // Print: YES, NSObject, NSObject, NSObject}else{? ? ? ? NSLog(@"NO");}}
打印信息如下:
SubClass, SuperClassYES, SubClass, SuperClass,NSObjectSuperClass,NSObjectYES, SuperClass,NSObject,NSObjectYES,UIView,UIResponder,NSObjectYES,NSObject,NSObject,NSObject
這里需要注意的是:object_getClass()可以獲得當(dāng)前對(duì)象isa。這里以 SubClass 相關(guān)的打印信息為例,來(lái)解釋一下:
SubClass,SuperClassYES,SubClass,SuperClass, NSObject
首先我們通過(guò) object_getClass() 獲取實(shí)例 sub 所屬的 Class(isa) 是SubClass;通過(guò) class_getSuperclass() 我們可以獲取 SubClass 對(duì)應(yīng)的父類(lèi)是SuperClass;通過(guò) objc_getMetaClass() 指定類(lèi)名,我們可以獲取對(duì)應(yīng)的元類(lèi),通過(guò) class_isMetaClass() 我們可以判斷一個(gè) Class 是否為元類(lèi),這里確認(rèn)后,打出YES;接著,打印元類(lèi)類(lèi)名是SubClass;打印元類(lèi)的父類(lèi)是SuperClass;再通過(guò) object_getClass() 獲得元類(lèi)的 isa,是NSObject。
對(duì)于 SuperClass 和 UIView 的相關(guān)打印信息解釋也同理。從這些的打印信息可以看出,與前文中給出的圖中的關(guān)系結(jié)構(gòu)是一致的。
動(dòng)態(tài)操作類(lèi)與實(shí)例的示例代碼
接著上面的代碼,我們繼續(xù):
int32_t testRuntimeMethodIMP(id self,SEL_cmd,NSDictionary*dic) {NSLog(@"testRuntimeMethodIMP: %@", dic);? ? //Print:? ? // testRuntimeMethodIMP: {? ? //? ? a ="para_a";? ? //? ? b ="para_b";? ? // }return99;}- (void)runtimeConstruct {#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wundeclared-selector"http://1:Createandregister class, addmethodto class.Classcls = objc_allocateClassPair(SuperClass.class,"RuntimeSubClass",0);? ? //Methodreturns:"int32_t"; accepts:"id self","SEL _cmd","NSDictionary *dic".Souse"i@:@"here.? ? class_addMethod(cls, @selector(testRuntimeMethod), (IMP) testRuntimeMethodIMP,"i@:@");? ? //Youcan only register a class once.? ? objc_registerClassPair(cls);? ? //2:Createinstanceofclass, print some info about classandassociated meta class.? ? id sub = [[cls alloc] init];NSLog(@"%@, %@", object_getClass(sub), class_getSuperclass(object_getClass(sub))); //Print:RuntimeSubClass,SuperClassClassmetaCls = objc_getMetaClass("RuntimeSubClass");if(class_isMetaClass(metaCls)) {NSLog(@"YES, %@, %@, %@", metaCls, class_getSuperclass(metaCls), object_getClass(metaCls)); //Print:YES,RuntimeSubClass,SuperClass,NSObject}else{NSLog(@"NO");? ? }? ? //3:Methodsofclass.? ? unsignedintoutCount =0;Method*methods = class_copyMethodList(cls, &outCount);for(int32_t i =0; i < outCount; i++) {Methodmethod= methods[i];NSLog(@"%@, %s",NSStringFromSelector(method_getName(method)), method_getTypeEncoding(method));? ? }? ? //Print: testRuntimeMethod, i@:@? ? free(methods);? ? //4:Callmethod.? ? int32_tresult= (int) [sub performSelector:@selector(testRuntimeMethod) withObject:@{@"a":@"para_a", @"b":@"para_b"}];NSLog(@"%d",result); //Print:99//5:Destoryinstancesandclass.? ? //Destroyinstancesofcls class before destroy cls class.? ? sub =nil;? ? //Donotcall this functionifinstancesofthe cls classoranysubclass exist.? ? objc_disposeClassPair(cls);#pragma clang diagnostic pop}
執(zhí)行上面代碼得到的打印信息如下:
RuntimeSubClass, SuperClassYES, RuntimeSubClass, SuperClass, NSObjecttestRuntimeMethod, i@:@testRuntimeMethodIMP:{? ? a ="para_a";b="para_b";}99
在上面的代碼中,我們?cè)谶\(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建了 SuperClass 的一個(gè)子類(lèi):RuntimeSubClass;接著為這個(gè)類(lèi)添加了方法和實(shí)現(xiàn);打印了 RuntimeSubClass 的類(lèi)、父類(lèi)、元類(lèi)相關(guān)信息;遍歷和打印了 RuntimeSubClass 的方法的相關(guān)信息;調(diào)用了 RuntimeSubClass 的方法;最后銷(xiāo)毀了實(shí)例和類(lèi)。
上面代碼中,有幾點(diǎn)在這里說(shuō)明一下:
我們看到了幾行#pragma clang diagnostic...代碼,這是用于忽略編譯器對(duì)于未聲明的 @selector 的 warning。因?yàn)槲覀兊拇a中我們需要?jiǎng)討B(tài)的為一個(gè)類(lèi)創(chuàng)建方法,所以必然不會(huì)事先聲明。
class_addMethod()函數(shù)的最后一個(gè)參數(shù) types 是描述方法返回值和參數(shù)列表的字符串,我們的代碼中的用到的i@:@四個(gè)字符分別對(duì)應(yīng)著:返回值 int32_t、參數(shù) id self、參數(shù) SEL _cmd、參數(shù) NSDictionary *dic。這個(gè)其實(shí)就是類(lèi)型編碼(Type Encoding)的概念。在 Objective-C 中,為了協(xié)助 Runtime 系統(tǒng),編譯器會(huì)將每個(gè)方法的返回值和參數(shù)列表編碼為一個(gè)字符串,這個(gè)字符串會(huì)與方法對(duì)應(yīng)的 selector 關(guān)聯(lián)。更詳細(xì)的知識(shí)可以查閱 [Type Encodings][6]。
使用objc_registerClassPair()函數(shù)需要注意,你不能注冊(cè)已經(jīng)注冊(cè)過(guò)的類(lèi)。
使用objc_disposeClassPair()函數(shù)需要注意,如果一個(gè)類(lèi)的實(shí)例和子類(lèi)還存在時(shí),不要去銷(xiāo)毀一個(gè)類(lèi)。
關(guān)于更多 Runtime 函數(shù)的使用細(xì)節(jié)可以查閱Objective-C Runtime Reference。
運(yùn)行時(shí)的成員變量與屬性
相關(guān)函數(shù)
Runtime 中與成員變量和屬性相關(guān)的函數(shù)有很多,這里列出一些:
Ivar class_getClassVariable(Class cls, const char *name),返回指定類(lèi)的指定名字的成員變量。
Ivar *class_copyIvarList(Class cls, unsigned int *outCount),返回指定類(lèi)的成員變量列表。調(diào)用后需要自己 free()。
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types),給指定的類(lèi)添加成員變量。這個(gè)函數(shù)只能在 objc_allocateClassPair() 和 objc_registerClassPair() 之間調(diào)用,并且不能為一個(gè)已經(jīng)存在的類(lèi)添加成員變量。
id object_getIvar(id obj, Ivar ivar),獲得對(duì)象的指定成員變量的值。速度比 object_getInstanceVariable() 快。
void object_setIvar(id obj, Ivar ivar, id value),設(shè)置對(duì)象指定成員變量的值。速度比 object_setInstanceVariable() 快。
Ivar object_getInstanceVariable(id obj, const char *name, void **outValue),獲取指定名字的成員變量的值。
Ivar object_setInstanceVariable(id obj, const char *name, void *value),設(shè)置指定名字成員變量的值。
const char *ivar_getName(Ivar v),獲取成員變量名。
const char *ivar_getTypeEncoding(Ivar v),獲取成員變量的類(lèi)型編碼。
ptrdiff_t ivar_getOffset(Ivar v),獲取成員變量的偏移量。
objc_property_t class_getProperty(Class cls, const char *name), 獲取指定類(lèi)指定名字的屬性。
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount), 獲取指定類(lèi)的屬性列表。調(diào)用后需要自己 free()。
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount), 給指定的類(lèi)添加屬性。
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount),替代指定類(lèi)的屬性。
const char *property_getName(objc_property_t property),獲取屬性名。
const char *property_getAttributes(objc_property_t property),獲取屬性特性描述。
objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount),獲取屬性特性列表。調(diào)用后需要自己 free()。
char *property_copyAttributeValue(objc_property_t property, const char *attributeName),獲取屬性特性值。調(diào)用后需要自己 free()。
成員變量(Ivar)的數(shù)據(jù)結(jié)構(gòu)
在 Objective-C 中成員變量即Ivar類(lèi)型,是指向struct objc_ivar結(jié)構(gòu)體的指針。可以在 objc/runtime.h 中查到:
typedefstructobjc_ivar *Ivar;
而 struct objc_ivar 結(jié)構(gòu)體的數(shù)據(jù)結(jié)構(gòu)如下:
structobjc_ivar {char*ivar_name OBJC2_UNAVAILABLE;// 變量名。char*ivar_type OBJC2_UNAVAILABLE;// 變量類(lèi)型。intivar_offset OBJC2_UNAVAILABLE;// 基地址偏移量,在對(duì)成員變量尋址時(shí)使用。#ifdef __LP64__intspace OBJC2_UNAVAILABLE;#endif}
屬性的數(shù)據(jù)結(jié)構(gòu)
屬性(Property)的數(shù)據(jù)結(jié)構(gòu):
typedefstructobjc_property *objc_property_t;
屬性特性(Attribute)的數(shù)據(jù)結(jié)構(gòu):
/// Defines a property attributetypedefstruct{constchar*name;/**< The name of the attribute */constchar*value;/**< The value of the attribute (usually empty) */}objc_property_attribute_t;
屬性和成員變量的聯(lián)系
本質(zhì)上一個(gè)屬性背后必然對(duì)應(yīng)著一個(gè)成員變量,但是屬性又不僅僅只是一個(gè)成員變量,屬性還會(huì)根據(jù)自己對(duì)應(yīng)的屬性特性的定義來(lái)對(duì)這個(gè)成員變量進(jìn)行一系列的封裝:提供 Getter/Setter 方法、內(nèi)存管理策略、線(xiàn)程安全機(jī)制等等。
運(yùn)行時(shí)操作成員變量和屬性的代碼示例
接著放示例代碼:
NSString * runtimePropertyGetterIMP(id self, SEL _cmd) {? ? Ivar ivar = class_getInstanceVariable([self class],"_runtimeProperty");returnobject_getIvar(self, ivar);}void runtimePropertySetterIMP(id self, SEL _cmd, NSString *s) {? ? Ivar ivar = class_getInstanceVariable([self class],"_runtimeProperty");NSString *old = (NSString *) object_getIvar(self, ivar);if(![old isEqualToString:s]) {? ? ? ? object_setIvar(self, ivar, s);}}- (void)aboutIvarAndProperty {#pragma clang diagnostic push#pragma clang diagnostic ignored"-Wundeclared-selector"http:// 1: Add property and getter/setter.Classcls= objc_allocateClassPair(SuperClass.class,"RuntimePropertySubClass",0);BOOL b = class_addIvar(cls,"_runtimeProperty", sizeof(cls), log2(sizeof(cls)), @encode(NSString));NSLog(@"%@", b ? @"YES": @"NO"); // Print: YESobjc_property_attribute_t type ={"T", "@\"NSString\""};objc_property_attribute_t ownership ={"C", ""}; // C = copyobjc_property_attribute_t isAtomic ={"N", ""}; // N = nonatomicobjc_property_attribute_t backingivar? ={"V", "_runtimeProperty"};objc_property_attribute_t attrs[] = {type, ownership, isAtomic, backingivar};class_addProperty(cls,"runtimeProperty", attrs,4);class_addMethod(cls, @selector(runtimeProperty), (IMP) runtimePropertyGetterIMP,"@@:");class_addMethod(cls, @selector(setRuntimeProperty), (IMP) runtimePropertySetterIMP,"v@:@");// You can only register a class once.objc_registerClassPair(cls);// 2: Print all properties.unsignedintoutCount =0;objc_property_t *properties = class_copyPropertyList(cls, &outCount);for(int32_t i =0; i < outCount; i++) {objc_property_t property = properties[i];NSLog(@"%s, %s\n", property_getName(property), property_getAttributes(property));}// Print:// runtimeProperty, T@"NSString",C,N,V_runtimePropertyfree(properties);// 3: Print all ivars.Ivar *ivars = class_copyIvarList(cls, &outCount);for(int32_t i =0; i < outCount; i++) {Ivar ivar = ivars[i];NSLog(@"%s, %s\n", ivar_getName(ivar), ivar_getTypeEncoding(ivar));}// Print:// _runtimeProperty, {NSString=#}free(ivars);// 4: Use runtime property.id sub = [[clsalloc] init];[sub performSelector:@selector(setRuntimeProperty) withObject:@"It-is-a-runtime-property."];NSString *s = [sub performSelector:@selector(runtimeProperty)]; //[sub valueForKey:@"runtimeProperty"];NSLog(@"%@", s); // Print: It-is-a-runtime-property.// 5: Clear.// Destroy instances of cls class before destroy cls class.sub = nil;// Do not call this function if instances of the cls class or any subclass exist.objc_disposeClassPair(cls);#pragma clang diagnostic pop}
上面代碼的打印信息如下:
YESruntimeProperty, T@"NSString",C,N,V_runtimeProperty_runtimeProperty, {NSString=#}It-is-a-runtime-property.
上面的代碼中,我們?cè)谶\(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建了 SuperClass 的一個(gè)子類(lèi)RuntimePropertySubClass;然后為它動(dòng)態(tài)添加了 Ivar:_runtimeProperty、對(duì)應(yīng)的 Property:runtimeProperty、對(duì)應(yīng)的 Getter/Setter:runtimePropertysetRuntimeProperty;接著我們遍歷和打印了 RuntimePropertySubClass 的 Ivar 列表和 Property 列表;然后創(chuàng)建了 RuntimePropertySubClass 的一個(gè)實(shí)例sub,并使用了 Property;最后我們清理了 sub 和 RuntimePropertySubClass。
這里有幾點(diǎn)需要注意的:
我們不能用 class_addIvar() 函數(shù)為一個(gè)已經(jīng)存在的類(lèi)添加 Ivar。并且 class_addIvar() 只能在 objc_allocateClassPair() 和 objc_registerClassPair() 之間調(diào)用。
添加屬性特性時(shí)的各種類(lèi)型字符可以參考:[Property Type String][8]。
添加一個(gè)屬性及對(duì)應(yīng)的成員變量后,我們還能通過(guò)[obj valueForKey:@"propertyName"];獲得屬性值。
運(yùn)行時(shí)的消息分發(fā)
相關(guān)函數(shù)
id objc_msgSend(id self, SEL op, ...),消息分發(fā)。(objc/message.h)
id method_invoke(id receiver, Method m, ...);,調(diào)用指定方法的實(shí)現(xiàn)。
void method_invoke_stret(id receiver, Method m, ...);,調(diào)用返回一個(gè)數(shù)據(jù)結(jié)構(gòu)的方法的實(shí)現(xiàn)。
SEL method_getName(Method m);,獲取方法名。
IMP method_getImplementation(Method m);,返回方法的實(shí)現(xiàn)。
const char * method_getTypeEncoding(Method m);,獲取描述方法參數(shù)和返回值類(lèi)型的字符串。
char * method_copyReturnType(Method m);,獲取方法的返回值類(lèi)型的字符串。
char * method_copyArgumentType(Method m, unsigned int index);,獲取方法的指定位置參數(shù)的類(lèi)型字符串。
void method_getReturnType(Method m, char *dst, size_t dst_len);,通過(guò)引用返回方法的返回值類(lèi)型字符串。
unsigned int method_getNumberOfArguments(Method m);,返回方法的參數(shù)的個(gè)數(shù)。
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len);,通過(guò)引用返回方法指定位置參數(shù)的類(lèi)型字符串。
struct objc_method_description * method_getDescription(Method m);,返回指定方法的方法描述結(jié)構(gòu)體。
IMP method_setImplementation(Method m, IMP imp);,設(shè)置方法的實(shí)現(xiàn)。注意該函數(shù)返回值是方法之前的實(shí)現(xiàn)。
void method_exchangeImplementations(Method m1, Method m2);,交換兩個(gè)方法的實(shí)現(xiàn)。
const char * sel_getName(SEL sel);,返回給定選擇器指定的方法的名稱(chēng)。
SEL sel_registerName(const char *str);,在Objective-C Runtime系統(tǒng)中注冊(cè)一個(gè)方法,將方法名映射到一個(gè)選擇器,并返回這個(gè)選擇器。
SEL sel_getUid(const char *str);,在Objective-C Runtime系統(tǒng)中注冊(cè)一個(gè)方法。
BOOL sel_isEqual(SEL lhs, SEL rhs);,比較兩個(gè)選擇器。
消息機(jī)制相關(guān)的數(shù)據(jù)結(jié)構(gòu)
選擇器
選擇器在 Objective-C 中即 SEL 類(lèi)型。它的定義如下(objc/objc.h):
typedefstructobjc_selector *SEL;
表示一個(gè)方法 selector 的指針。selector 用于表示運(yùn)行時(shí)方法的名字,Objective-C 在編譯時(shí)會(huì)根據(jù)每個(gè)方法的名字為方法生成一個(gè)唯一的整型標(biāo)識(shí)來(lái)替代方法名,這個(gè)整型標(biāo)識(shí)就是 SEL。比如:
SEL sel = @selector(alloc);NSLog(@"%p", sel); // Print: 0x10338b545
只要方法名相同,即使方法是否在不同的類(lèi)中,它生成的 SEL 也是一樣的。所以,SEL 沒(méi)干啥,它就是唯一標(biāo)識(shí)一個(gè)方法名而已。
這樣看來(lái),在一個(gè)類(lèi)中是不能存在兩個(gè)同名的方法的,即使參數(shù)類(lèi)型不同也不行。比如下面的兩個(gè)方法放在同一個(gè)類(lèi)中是無(wú)法編譯通過(guò)的:
- (void)test:(int)i;- (int)test:(double)d;
這個(gè)很好理解:一個(gè)類(lèi)的實(shí)例去調(diào)用方法時(shí)會(huì)通過(guò)方法的 SEL 去映射,如果存在相同的 SEL,那就不知道調(diào)用誰(shuí)了。
當(dāng)然,不同的類(lèi)是可以有相同的 SEL 的,即使這些類(lèi)之間是繼承關(guān)系也沒(méi)問(wèn)題。這是因?yàn)椴煌念?lèi),他們調(diào)用方法時(shí)的對(duì)象實(shí)例是不一樣的,那么對(duì)應(yīng)的方法列表和 SEL 就是不一樣的,不會(huì)有沖突。就算存在繼承關(guān)系,也是先找子類(lèi)的方法列表,沒(méi)有時(shí)再找父類(lèi)的方法列表,也不會(huì)有問(wèn)題。
在一個(gè)工程中,所有的 SEL 會(huì)組成一個(gè) Set 集合,這就意味著不會(huì)有重復(fù)的 SEL,這個(gè)也是可以理解的,首先這樣可以去掉重復(fù)的 SEL(不同類(lèi)的同名方法生成的相同 SEL),從而降低集合大小、提高查找性能;其次,調(diào)用時(shí)根據(jù)對(duì)象去查找對(duì)應(yīng)的方法列表,也不會(huì)有問(wèn)題。
所以綜上所述,SEL 確實(shí)沒(méi)干啥,它就是唯一標(biāo)識(shí)一個(gè)方法名而已。
在編譯時(shí),我們通過(guò) @selector 來(lái)獲取指定方法名的 SEL:
SELaSelector =@selector(methodName);
在運(yùn)行時(shí),我們通過(guò) NSSelectorFromString() 來(lái)獲得指定方法名的 SEL:
SEL aSelector = NSSelectorFromString(@"methodName");
函數(shù)指針
函數(shù)指針在 Objective-C 中即 IMP 類(lèi)型。它的定義如下(objc/objc.h):
typedef id (*IMP)(id,SEL, ...);
IMP 其實(shí)就是implementation的縮寫(xiě),表示方法實(shí)現(xiàn)的代碼塊地址,可以像 C 函數(shù)一樣直接調(diào)用。通常情況下,我們都是通過(guò)[object method:parameter]或objc_msgSend()的方式調(diào)用方法或函數(shù),然后 Runtime 去尋找消息匹配的 IMP 來(lái)調(diào)用,但有時(shí)候我們也可以直接獲取 IMP 來(lái)調(diào)用。通過(guò) IMP,我們可以跳過(guò) Rumtime 的消息分發(fā)流程,直接執(zhí)行 IMP 指向的代碼塊,這樣會(huì)比直接向?qū)ο蟀l(fā)送消息高效一些。這就是 IMP Caching 技術(shù)。
方法
方法在 Objective-C 中即 Method 類(lèi)型。它的定義如下(objc/runtime.h):
typedef struct objc_method *Method;struct objc_method{
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}OBJC2_UNAVAILABLE;
我們可以看到 Method 是struct objc_method指針類(lèi)型。struct objc_method 結(jié)構(gòu)中包含了一個(gè) SEL 和一個(gè) IMP,實(shí)際上是做了一個(gè) SEL 到 IMP 的映射,有了 SEL 我們就可以找到對(duì)應(yīng)的 IMP 來(lái)調(diào)用對(duì)應(yīng)的代碼。
此外,還有一個(gè)方法描述的數(shù)據(jù)結(jié)構(gòu)如下:
struct objc_method_description {SELname; /**
這里的描述了方法的 SEL 以及參數(shù)類(lèi)型。
消息分發(fā)機(jī)制說(shuō)明
Objective-C 中的消息是直到運(yùn)行時(shí)才綁定到具體的方法實(shí)現(xiàn)上。基本上代碼中形如[receiver message]的消息表達(dá)式在編譯階段只是確定了要向 receiver 發(fā)送 message 這樣一件事,這里的 message 是一個(gè)方法名 selector 以及相關(guān)的參數(shù)。這個(gè)消息會(huì)被編譯器轉(zhuǎn)化為對(duì)objc_msgSend()函數(shù)或相近函數(shù)的調(diào)用,在這個(gè)過(guò)程中,objc_msgSend() 函數(shù)會(huì)獲取 receiver、selector 以及 message 中的參數(shù)作為自己的參數(shù),調(diào)用形式如下:
objc_msgSend(receiver, selector, arg1, arg2, ...)
這個(gè)消息分發(fā)函數(shù)會(huì)在運(yùn)行時(shí)完成動(dòng)態(tài)綁定相關(guān)的事情,大體步驟如下:
首先找到 selector 對(duì)應(yīng)的確切的實(shí)現(xiàn)例程。由于同一個(gè)方法名可能會(huì)因?yàn)樗诘念?lèi)不同而實(shí)現(xiàn)不同,所以它對(duì)應(yīng)的確切的實(shí)現(xiàn)要依賴(lài)于類(lèi)和接受者 receiver。
調(diào)用對(duì)應(yīng)的實(shí)現(xiàn)例程,傳入 receiver 對(duì)象以及相關(guān)參數(shù)。
最后,將實(shí)現(xiàn)例程的返回值作為自己的返回值傳遞出去。
消息分發(fā)的奧義隱藏在編譯器為每個(gè)類(lèi)和對(duì)象構(gòu)造的數(shù)據(jù)結(jié)構(gòu)中,這個(gè)在我們前面的章節(jié)中以及介紹過(guò)了(struct objc_object 和 struct objc_class)。其中最關(guān)鍵的兩個(gè)字段:
指向父類(lèi)的指針(isa)。
類(lèi)的分發(fā)表(methodLists)。
消息分發(fā)的流程如圖所示:
當(dāng)消息發(fā)送給一個(gè)對(duì)象時(shí),objc_msgSend 通過(guò)對(duì)象的 isa 指針獲取到類(lèi)的結(jié)構(gòu)體,然后在方法分發(fā)表里面查找方法的 selector。如果沒(méi)有找到 selector,則通過(guò) objc_msgSend 結(jié)構(gòu)體中的指向父類(lèi)的指針找到其父類(lèi),并在父類(lèi)的分發(fā)表里面查找方法的 selector。依此,會(huì)一直沿著類(lèi)的繼承體系到達(dá) NSObject 類(lèi)。一旦定位到 selector,函數(shù)會(huì)就獲取到了實(shí)現(xiàn)的入口點(diǎn),并傳入相應(yīng)的參數(shù)來(lái)執(zhí)行方法的具體實(shí)現(xiàn)。如果最后沒(méi)有定位到 selector,則會(huì)走消息轉(zhuǎn)發(fā)流程。
此外,為了加速消息的處理,運(yùn)行時(shí)系統(tǒng)會(huì)緩存使用過(guò)的 selector 及對(duì)應(yīng)的方法的地址。這點(diǎn)在前面已經(jīng)討論過(guò),不再重復(fù)。
運(yùn)行時(shí)消息分發(fā)的代碼示例
Method Swizzling
在前文中講 Method 的數(shù)據(jù)結(jié)構(gòu)時(shí)我們說(shuō)到過(guò),方法的數(shù)據(jù)結(jié)構(gòu)中包含了 SEL 和 IMP。selector 相當(dāng)于一個(gè)方法的 id;IMP 是方法的實(shí)現(xiàn)。這樣分開(kāi)的一個(gè)便利之處是 selector 和 IMP 之間的對(duì)應(yīng)關(guān)系可以被改變。比如一個(gè) IMP 可以有多個(gè) selectors 指向它。而本節(jié)所講的 Method Swizzling 的概念則是交換兩個(gè)方法的實(shí)現(xiàn),從而「貍貓換太子」。
UIViewController+Runtime.m
#import"UIViewController+Runtime.h"#import@implementationUIViewController(Runtime)+ (void)load {staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{? ? ? ? Class aClass = [selfclass];? ? ? ? SEL originalSelector =@selector(viewWillAppear:);? ? ? ? SEL swizzledSelector =@selector(xxx_viewWillAppear:);? ? ? ? Method originalMethod = class_getInstanceMethod(aClass, originalSelector);? ? ? ? Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);// When swizzling a class method, use the following:// Class aClass = object_getClass((id)self);// ...// Method originalMethod = class_getClassMethod(aClass, originalSelector);// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);BOOLdidAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));if(didAddMethod) {? ? ? ? ? ? class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));? ? ? ? }else{? ? ? ? ? ? method_exchangeImplementations(originalMethod, swizzledMethod);? ? ? ? }? ? });}#pragma mark - Method Swizzling- (void)xxx_viewWillAppear:(BOOL)animated {NSLog(@"B1: %@",self);// Print: B1: [selfxxx_viewWillAppear:animated];NSLog(@"B2: %@",self);// Print: B2: }@end
ViewController.m
@implementationViewController#import"UIViewController+Runtime.h"- (void)viewWillAppear:(BOOL)animated {NSLog(@"A1: %@",self);// Print: A1: [superviewWillAppear:animated];NSLog(@"A2: %@",self);// Print: A2: }@end
在加載展示 ViewController 后,打印出的信息如下:
A1:B1:B2:A2:
上面的代碼有幾點(diǎn)需要說(shuō)明的:
一般來(lái)說(shuō),Method Swizzling 應(yīng)該在一個(gè)類(lèi)的+load方法實(shí)現(xiàn)。+load在一個(gè)類(lèi)最開(kāi)始被引用加載時(shí)就會(huì)調(diào)用。
使用 GCD 的dispatch_once來(lái)保證只調(diào)用一次,并且確保線(xiàn)程安全。
上面的代碼體現(xiàn)了兩個(gè)「分離」:
方法 SEL 和 IMP 的分離。方法的 SEL 和 IMP 可以綁定,也可以拆開(kāi)重綁。這是 Method Swizzling 的基礎(chǔ)。
對(duì)象和類(lèi)的方法列表的分離。對(duì)象和類(lèi)的方法列表的也是在運(yùn)行時(shí)根據(jù)類(lèi)的結(jié)構(gòu)進(jìn)行動(dòng)態(tài)綁定。
Method Swizzling 交換的是 UIViewController 類(lèi)的-viewWillAppear:和-xxx_viewWillAppear:的實(shí)現(xiàn),對(duì)其子類(lèi)并無(wú)影響。在代碼執(zhí)行的過(guò)程中,對(duì)象始終是 ViewController 的一個(gè)實(shí)例,只不過(guò)有時(shí)候它去調(diào)用了父類(lèi) UIViewController 的方法([super viewWillAppear:animated];)。代碼的執(zhí)行順序如圖所示:
消息轉(zhuǎn)發(fā)
當(dāng)一個(gè)對(duì)象能接收一個(gè)消息時(shí),就會(huì)走正常的方法調(diào)用流程。但如果一個(gè)對(duì)象無(wú)法接收指定消息時(shí),又會(huì)發(fā)生什么事呢?默認(rèn)情況下,如果是以[receiver message]的方式調(diào)用方法,如果 receiver 無(wú)法響應(yīng) message 消息時(shí),編譯器會(huì)報(bào)錯(cuò)。但如果是以performSelector…的形式來(lái)調(diào)用,則需要等到運(yùn)行時(shí)才能確定 receiver 是否能接收 message 消息。如果不能,則程序崩潰。
通常,當(dāng)我們不能確定一個(gè)對(duì)象是否能接收某個(gè)消息時(shí),會(huì)先調(diào)用respondsToSelector:來(lái)判斷一下:
if([self respondsToSelector:@selector(method)]) {? ? [self performSelector:@selector(method)];}
這里,我們想討論一下當(dāng)一個(gè)對(duì)象無(wú)法接收某一消息時(shí)的情況。一般這個(gè)時(shí)候,就會(huì)啟動(dòng)所謂消息轉(zhuǎn)發(fā)(message forwarding)機(jī)制,通過(guò)這一機(jī)制,我們可以告訴對(duì)象如何處理未知的消息。默認(rèn)情況下,對(duì)象接收到未知的消息,會(huì)導(dǎo)致程序崩潰:
-[ViewControllermethod]: unrecognized selector sent to instance0x7fa09b784f40Terminatingapp due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewControllermethod]: unrecognized selector sent to instance0x7fa09b784f40'
這段異常信息實(shí)際上是由 NSObject 的doesNotRecognizeSelector方法拋出的。不過(guò),我們可以采取一些措施,讓我們的程序執(zhí)行特定的邏輯,而避免程序的崩潰。
消息轉(zhuǎn)發(fā)機(jī)制基本上分為三個(gè)步驟:
第一步:動(dòng)態(tài)方法解析。
第二步:備用接收者。
第三步:完整轉(zhuǎn)發(fā)。
第一步:動(dòng)態(tài)方法解析
對(duì)象在接收到未知的消息時(shí),首先會(huì)調(diào)用所屬類(lèi)的類(lèi)方法+resolveInstanceMethod:或者+resolveClassMethod:,前者處理實(shí)例方法調(diào)用,后者處理類(lèi)方法調(diào)用。我們可以它們里面用class_addMethod()加入異常處理的方法,不過(guò)前提是我們以及實(shí)現(xiàn)了處理方法。示例代碼如下:
#import- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfperformSelector:@selector(unknownMethod)];}voiddealWithExceptionForUnknownMethod(idself, SEL _cmd) {NSLog(@"%@, %p",self, _cmd);// Print: , 0x1078259fc}+ (BOOL)resolveInstanceMethod:(SEL)sel {NSString*selectorString =NSStringFromSelector(sel);if([selectorString isEqualToString:@"unknownMethod"]) {? ? ? ? class_addMethod(self.class,@selector(unknownMethod), (IMP) dealWithExceptionForUnknownMethod,"v@:");? ? }return[superresolveInstanceMethod:sel];}
代碼打印:
,0x1078259fc
可以發(fā)現(xiàn)對(duì)unknownMethod方法的調(diào)用被截獲了并在dealWithExceptionForUnknownMethod函數(shù)中進(jìn)行了處理,程序沒(méi)有再崩潰。
@dynamic屬性就可以用這種方案來(lái)實(shí)現(xiàn)。
第二步:備用接收者
如果在第一步還是無(wú)法處理消息,則 Runtime 會(huì)繼續(xù)調(diào)以下方法:
-(id)forwardingTargetForSelector:(SEL)aSelector
如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法,并返回一個(gè)非 nil 的結(jié)果,則這個(gè)對(duì)象會(huì)作為消息的新接收者,且消息會(huì)被分發(fā)到這個(gè)對(duì)象。當(dāng)然這個(gè)對(duì)象不能是 self 自身,否則就會(huì)出現(xiàn)無(wú)限循環(huán)。當(dāng)然,如果我們沒(méi)有指定相應(yīng)的對(duì)象來(lái)處理 aSelector,則應(yīng)該調(diào)用父類(lèi)的實(shí)現(xiàn)來(lái)返回結(jié)果。示例代碼如下:
RuntimeMethodHelper.h
#import@interfaceRuntimeMethodHelper:NSObject- (void)unknownMethod2;@end
RuntimeMethodHelper.m
#import"RuntimeMethodHelper.h"@implementationRuntimeMethodHelper- (void)unknownMethod2 {NSLog(@"%@, %p",self, _cmd);// Print: , 0x10170d99a}@end
ViewController.m
#import- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfperformSelector:@selector(unknownMethod)];? ? [selfperformSelector:@selector(unknownMethod2)];}// Deal with unknownMethod.voiddealWithExceptionForUnknownMethod(idself, SEL _cmd) {NSLog(@"%@, %p",self, _cmd);// Print: , 0x1078259fc}+ (BOOL)resolveInstanceMethod:(SEL)sel {NSString*selectorString =NSStringFromSelector(sel);if([selectorString isEqualToString:@"unknownMethod"]) {? ? ? ? class_addMethod(self.class,@selector(unknownMethod), (IMP) dealWithExceptionForUnknownMethod,"v@:");? ? }return[superresolveInstanceMethod:sel];}// Deal with unknownMethod2.- (id)forwardingTargetForSelector:(SEL)aSelector {NSString*selectorString =NSStringFromSelector(aSelector);if([selectorString isEqualToString:@"unknownMethod2"]) {return[[RuntimeMethodHelper alloc] init];? ? }return[superforwardingTargetForSelector:aSelector];}
代碼打印信息:
,0x109c1f98c,0x109c1f99a
可以看到,對(duì)于unknownMethod方法已經(jīng)在第一步:動(dòng)態(tài)方法解析中被+resolveInstanceMethod處理,而unknownMethod2則放到了第二步:備用接收者才被處理。
這一步適用于當(dāng)我們只想將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上的情況,它無(wú)法進(jìn)一步對(duì)消息進(jìn)行處理,比如:操作消息的參數(shù)和返回值。
第三步:完整轉(zhuǎn)發(fā)
如果第二步:備用接收者還是未能處理好消息,那么接下來(lái)只有啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了,這時(shí)候會(huì)調(diào)用以下方法:
-(void)forwardInvocation:(NSInvocation *)anInvocation
運(yùn)行時(shí)系統(tǒng)會(huì)在這一步給消息接收者最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象。對(duì)象會(huì)創(chuàng)建一個(gè)表示消息的 NSInvocation 對(duì)象,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在 anInvocation 中,包括:selector、目標(biāo)(target)和參數(shù)。我們可以在-forwardInvocation:方法中選擇將消息轉(zhuǎn)發(fā)給其它對(duì)象。
當(dāng)你實(shí)現(xiàn)了-forwardInvocation:方法,你有兩個(gè)任務(wù)需要完成:
定位可以響應(yīng)封裝在 anInvocation 中的消息的對(duì)象。這個(gè)對(duì)象不需要能處理所有未知消息。
使用 anInvocation 作為參數(shù),將消息發(fā)送到選中的對(duì)象。anInvocation 將會(huì)保留調(diào)用結(jié)果,運(yùn)行時(shí)系統(tǒng)會(huì)提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者。
不過(guò),在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能,我們可以對(duì)消息的內(nèi)容進(jìn)行修改,比如追回一個(gè)參數(shù)等,然后再去觸發(fā)消息。另外,若發(fā)現(xiàn)某個(gè)消息不應(yīng)由本類(lèi)處理,則應(yīng)調(diào)用父類(lèi)的同名方法,以便繼承體系中的每個(gè)類(lèi)都有機(jī)會(huì)處理此調(diào)用請(qǐng)求。
另外還有一個(gè)重要的問(wèn)題是我們必須重寫(xiě)下面方法:
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息轉(zhuǎn)發(fā)機(jī)制使用從這個(gè)方法中獲取的信息來(lái)創(chuàng)建 NSInvocation 對(duì)象。因此我們必須重寫(xiě)這個(gè)方法,為給定的 selector 提供一個(gè)合適的方法簽名。
代碼示例如下:
RuntimeMethodHelper.h
#import@interfaceRuntimeMethodHelper:NSObject- (void)unknownMethod2;- (void)unknownMethod3;@end
RuntimeMethodHelper.m
#import"RuntimeMethodHelper.h"@implementationRuntimeMethodHelper- (void)unknownMethod2 {NSLog(@"%@, %p",self, _cmd);// Print: , 0x102d7991a}- (void)unknownMethod3 {NSLog(@"%@, %p",self, _cmd);// Print: , 0x102d79929}@end
ViewController.m
- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfperformSelector:@selector(unknownMethod)];? ? [selfperformSelector:@selector(unknownMethod2)];? ? ? ? ? [selfperformSelector:@selector(unknownMethod3)];}// Deal with unknownMethod.voiddealWithExceptionForUnknownMethod(id self, SEL _cmd) {? ? NSLog(@"%@, %p", self, _cmd);// Print: , 0x102d7990c}+ (BOOL)resolveInstanceMethod:(SEL)sel {? ? NSString *selectorString = NSStringFromSelector(sel);if([selectorStringisEqualToString:@"unknownMethod"]) {? ? ? ? class_addMethod(self.class,@selector(unknownMethod), (IMP) dealWithExceptionForUnknownMethod,"v@:");? ? }return[superresolveInstanceMethod:sel];}// Deal with unknownMethod2.- (id)forwardingTargetForSelector:(SEL)aSelector {? ? NSString *selectorString = NSStringFromSelector(aSelector);if([selectorStringisEqualToString:@"unknownMethod2"]) {return[[RuntimeMethodHelper alloc] init];? ? }return[superforwardingTargetForSelector:aSelector];}// Deal with unknownMethod3.- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {? ? NSMethodSignature *signature = [supermethodSignatureForSelector:aSelector];if(!signature) {if([RuntimeMethodHelperinstancesRespondToSelector:aSelector]) {? ? ? ? ? ? signature = [RuntimeMethodHelperinstanceMethodSignatureForSelector:aSelector];? ? ? ? }? ? }returnsignature;}- (void)forwardInvocation:(NSInvocation *)anInvocation {if([RuntimeMethodHelperinstancesRespondToSelector:anInvocation.selector]) {? ? ? ? [anInvocationinvokeWithTarget:[[RuntimeMethodHelper alloc] init]];? ? }}
代碼打印信息:
,0x102d7990c,0x102d7991a,0x102d79929
NSObject 的-forwardInvocation:方法實(shí)現(xiàn)只是簡(jiǎn)單調(diào)用了-doesNotRecognizeSelector:方法,它不會(huì)轉(zhuǎn)發(fā)任何消息。這樣,如果不在以上所述的三個(gè)步驟中處理未知消息,到了 NSObject 那則會(huì)引發(fā)一個(gè)異常。
從某種意義上來(lái)講,-forwardInvocation:就像一個(gè)未知消息的分發(fā)中心,將這些未知的消息轉(zhuǎn)發(fā)給其它對(duì)象。或者也可以像一個(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對(duì)象。這取決于具體的實(shí)現(xiàn)。
消息轉(zhuǎn)發(fā)與多重繼承
回過(guò)頭來(lái)看上面第二步和第三步,通過(guò)-forwardingTargetForSelector:和-forwardInvocation:這兩個(gè)方法我們可以允許一個(gè)對(duì)象與其它對(duì)象建立關(guān)系,以處理某些未知消息,而表面上看仍然是該對(duì)象在處理消息。通過(guò)這種關(guān)系,我們可以模擬多重繼承的某些特性,讓對(duì)象可以繼承其它對(duì)象的特性來(lái)處理一些事情。不過(guò),這兩者間有一個(gè)重要的區(qū)別:多重繼承將不同的功能集成到一個(gè)對(duì)象中,它會(huì)讓對(duì)象變得過(guò)大,涉及的東西過(guò)多;而消息轉(zhuǎn)發(fā)將功能分解到獨(dú)立的小的對(duì)象中,并通過(guò)某種方式將這些對(duì)象連接起來(lái),并做相應(yīng)的消息轉(zhuǎn)發(fā)。
不過(guò)消息轉(zhuǎn)發(fā)雖然類(lèi)似于繼承,但 NSObject 的一些方法還是能區(qū)分兩者。如respondsToSelector:和isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來(lái)像是繼承,則可以重寫(xiě)這些方法,如以下代碼所示:
- (BOOL)respondsToSelector:(SEL)aSelector {if([super respondsToSelector:aSelector]) {returnYES;? ? }else{? ? ? ? /* Here, test whethertheaSelector message can? ? *? ? ? ? * be forwardedtoanother objectandwhetherthat*? ? ? ? * object can respondtoit. Return YESifitcan.? */? ? }returnNO;? }
參考