參考內(nèi)容
Objective-C Runtime 運(yùn)行時(shí)之三:方法與消息
方法中 SEL, IMP, Method 的定義與關(guān)系

SEL
- 代表方法的名稱。僅以名字來識(shí)別。
- 不論兩個(gè)類是否存在依存關(guān)系,只要他們擁有相同的方法名,那么他們的
SEL
都是相同的。比如,有n個(gè)viewcontroller
頁面,每個(gè)頁面都有一個(gè)viewdidload
,每個(gè)頁面的載入,肯定都是不盡相同的。但是我們可以通過打印,觀察發(fā)現(xiàn),這些viewdidload
的SEL
都是同一個(gè):
SEL sel = @selector(methodName);
NSLog(@"address = %p",sel);// log輸出為 address = 0x100002d72
- 因此類方法定義時(shí),盡量不要用相同的名字,就算是變量類型不同也不行。否則會(huì)引起重復(fù),例如:
-(void)setWidth:(int)width;
-(void)setWidth:(double)width;
IMP
- 定義:
id (*IMP)(id, SEL, ...)
個(gè)人理解:
-
SEL
只提供一個(gè)名字,但是至于這個(gè)名字屬于誰,并不知道。A類和B類的SEL
實(shí)際實(shí)現(xiàn)IMP
是不一樣的。IMP
其實(shí)是implementation
的縮寫。 -
IMP
定義中存在兩個(gè)參數(shù),id
即為指向self的指針。SEL
就是上面說的方法名 - 綜合來看,
IMP
可以理解為,我知道有一個(gè)方法,是SEL
告訴我的,但是這是哪個(gè)類的里面具體的實(shí)現(xiàn)方法,需要id
告訴我。兩者確定后,我就可以獲得某個(gè)指定的方法的內(nèi)容了。IMP
相當(dāng)于是函數(shù)指針。
Method
- 我們可以看到該結(jié)構(gòu)體中包含一個(gè)SEL和IMP,實(shí)際上相當(dāng)于在SEL和IMP之間作了一個(gè)映射。有了SEL,我們便可以找到對(duì)應(yīng)的IMP,從而調(diào)用方法的實(shí)現(xiàn)代碼。具體操作流程我們將在下面討論。
- 個(gè)人理解,Method相當(dāng)于是把二者綜合在一起,是一個(gè)完整的,我們傳統(tǒng)意義上理解的方法
Method 方法及說明
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 獲取實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的實(shí)現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類實(shí)例是否響應(yīng)指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
說明
class_addMethod
的實(shí)現(xiàn)會(huì)覆蓋父類的方法實(shí)現(xiàn),但不會(huì)取代本類中已存在的實(shí)現(xiàn),如果本類中包含一個(gè)同名的實(shí)現(xiàn),則函數(shù)會(huì)返回NO。如果要修改已存在實(shí)現(xiàn),可以使用method_setImplementation
一個(gè)Objective-C方法是一個(gè)簡單的C函數(shù),它至少包含兩個(gè)參數(shù)–self
和_cmd
。所以,我們的實(shí)現(xiàn)函數(shù)(IMP參數(shù)指向的函數(shù))至少需要兩個(gè)參數(shù)class_getInstanceMethod
、class_getClassMethod函數(shù)
,與class_copyMethodList
不同的是,這兩個(gè)函數(shù)都會(huì)去搜索父類的實(shí)現(xiàn)。class_copyMethodList
函數(shù),返回包含所有實(shí)例方法的數(shù)組,如果需要獲取類方法,則可以使用class_copyMethodList(object_getClass(cls)
, &count)(一個(gè)類的實(shí)例方法是定義在元類里面)。該列表不包含父類實(shí)現(xiàn)的方法。outCount參數(shù)返回方法的個(gè)數(shù)。在獲取到列表后,我們需要使用free()方法來釋放它。class_replaceMethod
函數(shù),該函數(shù)的行為可以分為兩種:如果類中不存在name指定的方法,則類似于class_addMethod
函數(shù)一樣會(huì)添加方法;如果類中已存在name指定的方法,則類似于method_setImplementation
一樣替代原方法的實(shí)現(xiàn)。class_getMethodImplementation
函數(shù),該函數(shù)在向類實(shí)例發(fā)送消息時(shí)會(huì)被調(diào)用,并返回一個(gè)指向方法實(shí)現(xiàn)函數(shù)的指針。這個(gè)函數(shù)會(huì)比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函數(shù)指針可能是一個(gè)指向runtime
內(nèi)部的函數(shù),而不一定是方法的實(shí)際實(shí)現(xiàn)。例如,如果類實(shí)例無法響應(yīng)selector,則返回的函數(shù)指針將是運(yùn)行時(shí)消息轉(zhuǎn)發(fā)機(jī)制的一部分。class_respondsToSelector
函數(shù),我們通常使用NSObject
類的respondsToSelector:
或instancesRespondToSelector:
方法來達(dá)到相同目的。
示例,實(shí)現(xiàn) method swizzle
用runtime + category 實(shí)現(xiàn)
runtime
中 types
的說明
const char *types
一個(gè)定義該函數(shù)返回值類型和參數(shù)類型的字符串
舉例說明
- 函數(shù)原型
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
- 說明
class_addMethod([TestClass class], @selector(ocMethod:), (IMP)testFunc, "i@:@");
* `i`表示方法的返回類型,下面是`dash`中的截圖
* 第一個(gè)
@
是必須有的,表示self. 也就是說,上面說的返回類型+@
是必備的。*
:@
表示傳入的參數(shù)。如果有兩個(gè)參數(shù),就是:@@
.可參見下面的代碼
BOOL class_addMethod(Class cls,SEL name,IMP imp, const char *types)
/**
* 一個(gè)參數(shù)
*
*/
int cfunction(id self, SEL _cmd, NSString *str) {
NSLog(@"%@", str);
return10;//隨便返回個(gè)值
}
-(void) oneParam {
TestClass *instance = [[TestClassalloc]init];
// 方法添加
class_addMethod([TestClassclass],@selector(ocMethod:), (IMP)cfunction,"i@:@");
if ([instance respondsToSelector:@selector(ocMethod:)]) {
NSLog(@"Yes, instance respondsToSelector:@selector(ocMethod:)");
} else
NSLog(@"Sorry");
int a = (int)[instance ocMethod:@"我是一個(gè)OC的method,C函數(shù)實(shí)現(xiàn)"];
NSLog(@"a:%d", a);
}
/**
* 兩個(gè)參數(shù)
*
*/
int cfunctionA(id self, SEL _cmd, NSString *str, NSString *str1) {
NSLog(@"%@-%@", str, str1);
return20;//隨便返回個(gè)值
}
-(void) twoParam {
TestClass *instance = [[TestClass alloc]init];
class_addMethod([TestClass class],@selector(ocMethodA::), (IMP)cfunctionA,"i@:@@");
if ([instance respondsToSelector:@selector(ocMethodA::)]) {
NSLog(@"Yes, instance respondsToSelector:@selector(ocMethodA::)");
} else
NSLog(@"Sorry");
int a = (int)[instance ocMethodA:@"我是一個(gè)OC的method,C函數(shù)實(shí)現(xiàn)" :@"-----我是第二個(gè)參數(shù)"];
NSLog(@"a:%d", a);
}