在OC中,我們對方法的調(diào)用都會被轉(zhuǎn)換成內(nèi)部的消息發(fā)送執(zhí)行對objc_msgSend方法的調(diào)用,掌握好消息發(fā)送,可以讓我們在編程中更方便靈活。
首先來看下方法定義:
/** 定義:'為某個類對象發(fā)送消息喇颁,并且返回一個值'
參數(shù)1: 消息接收的對象實(shí)例
參數(shù)2: 要執(zhí)行的方法
...: 一系列其他參數(shù) */
id objc_msgSend(id self, SEL op, ...)
這里有官方文檔的解釋
我們創(chuàng)建一個MessageSendTest
文件漏健,在.m
文件中定義四個方法用于測試:
// 無參數(shù) 無返回值
- (void)noArgumentsAndNoReturnValue
{
NSLog(@"方法名:%s", __FUNCTION__);
}
// 帶一個參數(shù) 無返回值
- (void)hasArguments:(NSString *)arg
{
NSLog(@"方法名:%s, 參數(shù):%@", __FUNCTION__, arg);
}
// 無參數(shù) 有返回值
- (NSString *)noArgumentsButReturnValue
{
NSLog(@"方法名:%s, 返回值:%@", __FUNCTION__, @"不帶參數(shù),但是帶有返回值");
return @"不帶參數(shù)橘霎,但是帶有返回值";
}
// 帶兩個參數(shù) 有返回值
- (int)hasArguments:(NSString *)arg andReturnValue:(int)arg1
{
NSLog(@"方法名:%s, 參數(shù):%@, 返回值:%d", __FUNCTION__, arg, arg1);
return arg1;
}
然后我們再定義一個測試方法:
+ (void)test
{
}
在測試方法里邊蔫浆,我們:
-
調(diào)用無參無返回值方法
// 1、創(chuàng)建對象 // 給'MessageSendTest'類發(fā)送消息姐叁,創(chuàng)建對象瓦盛,這句話等同于 MessageSendTest *test = [MessageSendTest alloc]; MessageSendTest *test = ((MessageSendTest * (*)(id,SEL)) objc_msgSend)((id)[MessageSendTest class], @selector(alloc)); // 2、初始化對象 // 給'test'對象發(fā)送消息進(jìn)行初始化外潜,這句話等同于 [test init]; test = ((MessageSendTest *(*)(id,SEL))objc_msgSend)((id)test, @selector(init)); NSLog(@"test:%@", test); // 3原环、調(diào)用無參無返回值方法 ((void(*)(id,SEL))objc_msgSend)((id)test, @selector(noArgumentsAndNoReturnValue));
從上邊三行代碼我們不難看出,每次給對象發(fā)送消息处窥,
objc_msgSend
都至少要帶有(id, SEL)兩個參數(shù)嘱吗,其中'1'和'2'里邊返回值類型為MessageSendTest *
類型,'3'里邊返回值類型為void類型滔驾。這樣我們就創(chuàng)建了一個MessageSendTest
的對象谒麦,并且調(diào)用了noArgumentsAndNoReturnValue
方法打印結(jié)果:(由打印結(jié)果可見俄讹,我們已經(jīng)達(dá)到了預(yù)期目的)
2016-07-04 15:59:46.543 ZFRuntime[1378:216191] test:<MessageSendTest: 0x7fafe0c733d0> 2016-07-04 15:59:46.543 ZFRuntime[1378:216191] 方法名:-[MessageSendTest noArgumentsAndNoReturnValue]
-
調(diào)用帶一個參數(shù)但無返回值的方法
// 4、調(diào)用帶一個參數(shù)但無返回值的方法 ((void(*)(id,SEL,NSString *))objc_msgSend)((id)test, @selector(hasArguments:), @"帶一參數(shù)但是沒有返回值");
相比于上邊的方法绕德,這里我們在
=
左邊多了個NSString
和@"帶一參數(shù)但是沒有返回值"
患膛,這就是我們要給這個方法傳遞的參數(shù)打印結(jié)果:(由打印結(jié)果可見,我們已經(jīng)達(dá)到了預(yù)期目的)
2016-07-04 16:10:24.800 ZFRuntime[1415:225374] 方法名:-[MessageSendTest hasArguments:], 參數(shù):帶一參數(shù)但是沒有返回值
-
調(diào)用帶返回值耻蛇,但是不帶參數(shù)的方法
NSString *returnStr = ((NSString * (*) (id, SEL)objc_msgSend))((id)test, @selector(noArgumentsButReturnValue)); NSLog(@"5. 返回值為:%@", reuturnStr);
打印結(jié)果:(由打印結(jié)果可見踪蹬,我們已經(jīng)達(dá)到了預(yù)期目的)
2016-07-04 16:13:53.764 ZFRuntime[1434:229571] 方法名:-[MessageSendTest noArgumentsButReturnValue], 返回值:不帶參數(shù),但是帶有返回值 2016-07-04 16:13:53.764 ZFRuntime[1434:229571] 5. 返回值為:不帶參數(shù)城丧,但是帶有返回值
-
調(diào)用帶參數(shù)帶返回值的方法
int returnInt = ((int *(id, SEL, NSString *, int))objc_msgSend)((id)test, @selector(hasArguments:andReturnValue:), @"參數(shù)1", 1024); NSLog(@"6. return value is %d", reuturnInt);
打印結(jié)果:(由打印結(jié)果可見延曙,我們已經(jīng)達(dá)到了預(yù)期目的)
2016-07-04 16:18:38.679 ZFRuntime[1455:234403] 方法名:-[MessageSendTest hasArguments:andReturnValue:], 參數(shù):參數(shù)1, 返回值:1024 2016-07-04 16:18:38.679 ZFRuntime[1455:234403] 6. return value is 1024
-
我們還可以給類動態(tài)地添加方法
class_addMethod([test class], NSSelectorFromString(@"cStyleFunc"), (IMP)cStyleFunc, "i@:r^vr^v"); int returnValue = ((int *(id, SEL, const void *, const void *))objc_msgSend)((id)test, NSSelectorFromString(@"cStyleFunc"), "參數(shù)1", "參數(shù)2"); NSLog(@"7. 返回值:%d", returnValue); // 然后我們實(shí)現(xiàn)方法 `cStyleFunc` int cStyleFunc(id receiver, SEL sel, const void *arg1, const void *arg2) { NSLog(@"方法名:%s, 參數(shù)1:%@, 參數(shù)2:%@", __FUNCTION__, [NSString stringWithUTF8String:arg1], [NSString stringWithUTF8String:arg1]); return 1; }
先來看下打印結(jié)果:
2016-07-04 16:24:36.670 ZFRuntime[1477:241676] 方法名:cStyleFunc, 參數(shù)1:參數(shù)1, 參數(shù)2:參數(shù)1 2016-07-04 16:24:36.670 ZFRuntime[1477:241676] 7. 返回值:1
從打印來看,結(jié)果是正常的亡哄,那么我們再來分析下
class_addMethod
這個方法枝缔,對于這個API蘋果是這樣定義的/** * 定義:給定名稱和實(shí)現(xiàn),從而為類增加新的方法 * cls:要增加方法的那個類 * name:方法選擇器 * imp:具體的實(shí)現(xiàn)函數(shù) * types:一串描述方法參數(shù)的字符串 */ BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
對于上邊這個方法蚊惯,參數(shù)1愿卸、2和3我們都沒有疑惑,可是第4個參數(shù)截型,讓人看著云里霧里趴荸,完全不知道是什么東西,其實(shí)查看官方文檔不難發(fā)現(xiàn)
對于
"i@:r^vr^v"
第1個字符:表示函數(shù)(方法)返回值類型宦焦,這里返回值類型是 `int` 发钝,故為 `i` 第2、3個字符:蘋果解釋是由于函數(shù)(方法)至少帶有兩個參數(shù)(self和_cmd)還記得之前的 (id,SEL) 么波闹,所以第2酝豪、3個字符必須是 ‘@:’,其實(shí)我們當(dāng)做固定寫法就好了 第4個字符之后的是什么呢精堕?
不要著急孵淘,我們在剛才的輸出語句
NSLog(@"7. 返回值:%d", returnValue);
之后加一句打印:NSLog(@"%s", @encode(const void *));
這個時候控制臺輸出了一句話:
2016-07-04 16:44:32.300 ZFRuntime[1522:262609] r^v
看到這里我們就明白了歹篓,
r^v
其實(shí)代表的是cStyleFunc
函數(shù)第3瘫证、4個參數(shù)類型,當(dāng)然對于不同情況庄撮,這里的r^v
是不同的背捌,具體其他情況可以參考蘋果給出的解釋
備注
在使用objc_msgSend
方法編譯時可能出現(xiàn)報錯的情況,對應(yīng)的解決辦法如下:
本篇筆記部分參考自一下: