使用 NSInvocation 向?qū)ο蟀l(fā)送消息

1. Objective-C 的消息派發(fā)

Objective-C 是動態(tài)語言,所有的消息都是在 Runtime 進(jìn)行派發(fā)的

1.1. objc_msgSend

?最底層的轉(zhuǎn)發(fā)函數(shù)為objc_msgSend胃珍,它的定義如下

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)

從以上的定義我們可以得出一個消息轉(zhuǎn)發(fā)包含了幾大要素:target概页、selector催跪、arguments、return value,objc_msgSend 是 C 函數(shù)硬纤,蘋果不提倡我們直接使用該函數(shù)來向?qū)ο笙ⅰ?/p>

1.2. performSelector

想必大家都知道使用 performSelector 給對象發(fā)送消息膳灶,但是其有幾個短板

  • 在 ARC 場景下 performSelector 可能會造成內(nèi)存泄漏
  • performSelector 至多接收 2 個參數(shù)咱士,如果參數(shù)多余 2 個,我們就無法使用 performSelector 來向?qū)ο蟀l(fā)送消息了轧钓。
  • performSelector 限制參數(shù)類型為 id序厉,以標(biāo)量數(shù)據(jù)(int double NSInteger 等)為參數(shù)的方法使用 performSelector 調(diào)用會出現(xiàn)各種各樣詭異的問題

1.3. NSInvocation

NSInvocation 是蘋果工程師們提供的一個高層的消息轉(zhuǎn)發(fā)系統(tǒng)。它是一個命令對象毕箍,可以給任何 Objective-C 對象類型發(fā)送消息弛房,接下來將介紹 NSInvocation 的?用法。

2. NSInvocation 的使用

2.1. 初始化

必須使用工廠方法 invocationWithMethodSignature: 來創(chuàng)建一個 NSInvocation 實(shí)例而柑。工廠方法的參數(shù)是一個 NSMethodSignature 對象文捶。一般使用 NSObject 的實(shí)例方法 methodSignatureForSelector: 或者類方法 instanceMethodSignatureForSelector: 來創(chuàng)建對應(yīng) selector 的 NSMethodSignature 對象。

例:創(chuàng)建類方法的簽名與實(shí)例方法簽名

- (void)createClassMethodSignature:(SEL)selector {
    NSMethodSignature *methodSignature = [[self class] methodSignatureForSelector:selector];
}

- (void)createInstanceMethodSignature:(SEL)selector {
    NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector];
}

2.2. 接受對象以及選擇子

需要注意的是 NSMethodSignature 對象僅僅表示了方法的簽名:方法的請求媒咳、返回數(shù)據(jù)的編碼粹排。所以在使用 NSMethodSignature 來創(chuàng)建 NSInvocation 對象之后仍需指定消息的接收對象和選擇子

NSMethodSignature *methodSignature = [[self class] methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:[self class]];
[invocation setSelector:selector];

原則是接收對象的對應(yīng)選擇子需要跟 NSMethodSignature 相匹配涩澡。但是根據(jù)實(shí)踐來說顽耳,只要不造成 NSInvocation setArgument:atIndex 越界的異常,都是可以成功轉(zhuǎn)發(fā)消息的妙同,并且轉(zhuǎn)發(fā)成功之后射富,未賦值的參數(shù)都將被賦值為 nil。

例如:

- (void)greetingWithInvocation {
    NSMethodSignature *methodSignature = [self methodSignatureForSelector:@selector(greetingWithName:)];
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    [invocation setSelector:@selector(greetingWithAge:name:)];
    
//    NSString *name = @"Tom";
//    [invocation setArgument:&name atIndex:3];
    NSUInteger age = 10;
    [invocation setArgument:&age atIndex:2];
    
    [invocation invokeWithTarget:self];
}

- (void)greetingWithName:(NSString *)name {
    NSLog(@"Hello World %@!",name);
}

- (void)greetingWithAge:(NSUInteger)age name:(NSString *)name {
    NSLog(@"Hello %@ %ld!", name, (long)age);
}

執(zhí)行結(jié)果:

2017-05-03 16:16:29.815 NSInvocationDemo[50214:49610519] Hello (null) 10!

2.3. 參數(shù)傳遞

- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

以上為 NSInvocation 類中定義針對參數(shù)的操作粥帚。 argumentLocation 參數(shù)為 void * 類型辉浦,表示需要傳遞指針地址給它。idx 參數(shù)是從 2 開始的茎辐,0 和 1 分別代表 target 和 selector宪郊,雖然可以?直接使用 getArgument:atIndex 來獲取 target 和 selector掂恕,但是不如 NSInvocation 的 target 以及 selector 屬性來的方便。需要注意的是當(dāng) idx 超過對應(yīng) NSMethodSignature 的參數(shù)個數(shù)的時候獲取參數(shù)和設(shè)置參數(shù)的方法都會拋出 NSInvalidArgumentException 異常弛槐。

例如:給 greetingWithName: 方法傳參

- (void)sendMsgWithInvocation {
    NSString *name = @"Tom";
    SEL selector = @selector(greetingWithName:);

    NSMethodSignature *methodSignature = [self methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    [invocation setTarget:self];
    [invocation setSelector:selector];
    [invocation setArgument:&name atIndex:2];
    [invocation invoke];
}

- (void)greetingWithName:(NSString *)name {
    NSLog(@"Hello %@!", name);
}

需要特別注意 setArgument:atIndex: 默認(rèn)不會強(qiáng)引用它的 argument懊亡,如果 argument 在 NSInvocation 執(zhí)行的時候之前被釋放就會造成野指針異常(EXC_BAD_ACCESS)。

NSInvocation_Crash.png

如上圖所示乎串, invocation 未?強(qiáng)引用它的 target店枣,在控制器彈出之后,target ?被釋放叹誉,然后再 invoke 這個 invocation 會造成野指針異常鸯两。調(diào)用 retainArguments 方法來強(qiáng)引用參數(shù)(包括 target 以及 selector)

2.4. 返回數(shù)據(jù)

NSInvocation 類中的返回數(shù)據(jù)的方法如下

- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;

可以看到返回數(shù)據(jù)仍然是通過傳入指針來進(jìn)傳值的长豁。例:

- (void)plusWithInvocation {
    NSMethodSignature *methodSignature = [self methodSignatureForSelector:@selector(plusWithA:B:)];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    [invocation retainArguments];
    [invocation setTarget:self];
    [invocation setSelector:@selector(plusWithA:B:)];
    
    int a = 10;
    [invocation setArgument:&a atIndex:2];
    int b = 5;
    [invocation setArgument:&b atIndex:3];
    
    [invocation invoke];
    
    int result;
    [invocation getReturnValue:&result];
    NSLog(@"%ld", (long)result);
}

- (int)plusWithA:(int)a B:(int)b {
    return a + b;
}

輸出結(jié)果為:

2017-05-03 17:13:31.884 NSInvocationDemo[50948:49713408] 15

需要注意的是:考慮到 getReturnValue 方法僅僅是將返回數(shù)據(jù)拷貝到提供的緩存區(qū)(retLoc)內(nèi)钧唐,并不會考慮到此處的內(nèi)存管理,所以如果返回數(shù)據(jù)是對象類型的匠襟,實(shí)際上獲取到的返回數(shù)據(jù)是 __unsafe_unretained 類型的钝侠,上層函數(shù)再?把它作為返回數(shù)據(jù)返回的時候就會造成野指針異常。通常的解決方法有2種:

第一種:新建一個相同類型的對象并指向它酸舍,這樣做 result 就會強(qiáng)引用 tempResult帅韧,當(dāng)做返回數(shù)據(jù)返回之后會自動添加 autorelease 關(guān)鍵字,也就不會造成野指針異常啃勉。

NSNumber __unsafe_unretained *tempResult;
[invocation getReturnValue:&tempResult];
NSNumber *result = tempResult;
return result;

第二種:?使用 __bridge 將緩存區(qū)轉(zhuǎn)換為 Objective-C 類型忽舟,這種做法其實(shí)跟第一種相似,但是我們更建議使用這種方式來解決以上問題淮阐,因?yàn)?getReturnValue ?本來就是給緩存區(qū)寫入數(shù)據(jù)叮阅,緩存區(qū)聲明為 void* 類型更為合理,然后通過 __bridge 方式轉(zhuǎn)換為 Objective-C 類型并?且將該內(nèi)存區(qū)的內(nèi)存管理交給 ARC枝嘶。

void *tempResult = NULL;
[invocation getReturnValue:&tempResult];
NSNumber *result = (__bridge NSNumber *)tempResult;
return result;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帘饶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子群扶,更是在濱河造成了極大的恐慌及刻,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竞阐,死亡現(xiàn)場離奇詭異缴饭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骆莹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門颗搂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人幕垦,你說我怎么就攤上這事丢氢「盗” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵疚察,是天一觀的道長蒸走。 經(jīng)常有香客問我,道長貌嫡,這世上最難降的妖魔是什么比驻? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮岛抄,結(jié)果婚禮上别惦,老公的妹妹穿的比我還像新娘。我一直安慰自己夫椭,他們只是感情好掸掸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著益楼,像睡著了一般猾漫。 火紅的嫁衣襯著肌膚如雪点晴。 梳的紋絲不亂的頭發(fā)上感凤,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音粒督,去河邊找鬼陪竿。 笑死,一個胖子當(dāng)著我的面吹牛屠橄,可吹牛的內(nèi)容都是我干的族跛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼锐墙,長吁一口氣:“原來是場噩夢啊……” “哼礁哄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起溪北,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤桐绒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后之拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茉继,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年蚀乔,在試婚紗的時候發(fā)現(xiàn)自己被綠了烁竭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡吉挣,死狀恐怖派撕,靈堂內(nèi)的尸體忽然破棺而出婉弹,到底是詐尸還是另有隱情,我是刑警寧澤终吼,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布马胧,位于F島的核電站,受9級特大地震影響衔峰,放射性物質(zhì)發(fā)生泄漏佩脊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一垫卤、第九天 我趴在偏房一處隱蔽的房頂上張望威彰。 院中可真熱鬧,春花似錦穴肘、人聲如沸歇盼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豹缀。三九已至,卻和暖如春慨代,著一層夾襖步出監(jiān)牢的瞬間邢笙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工侍匙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氮惯,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓想暗,卻偏偏與公主長得像妇汗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子说莫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容