單純消除 warning:
LLVM 3.0 編譯器可以用以下代碼消除 warning:
#pragmaclang diagnostic push#pragmaclang diagnostic ignored"-Warc-performSelector-leaks"[self.ticketTarget performSelector: self.ticketAction withObject: self];#pragmaclang diagnostic pop屈梁。
#define SuppressPerformSelectorLeakWarning(Stuff) \do{ \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \ Stuff; \ _Pragma("clang diagnostic pop") \ }while(0)
用的時(shí)候? SuppressPerformSelectorLeakWarning( [_target performSelector:_action withObject:self]);
答案:
if(!_controller) {return; }SEL selector =NSSelectorFromString(@"someMethod");IMP imp = [_controller methodForSelector:selector];void(*func)(id, SEL) = (void*)imp;func(_controller, selector);
代碼解釋
這一堆代碼在做的事情其實(shí)是裆馒,向 controller 請(qǐng)求那個(gè)方法對(duì)應(yīng)的 C 函數(shù)指針。所有的NSObject都能響應(yīng)methodForSelector:這個(gè)方法葛家,不過也可以用 Objective-C runtime 里的class_getMethodImplementation(只在 protocol 的情況下有用,id這樣的)泌类。這種函數(shù)指針叫做IMP癞谒,就是typedef過的函數(shù)指針(id (*IMP)(id, SEL, ...)[1])。它跟方法簽名(signature)比較像,雖然可能不是完全一樣弹砚。
得到IMP之后双仍,還需要進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換后的函數(shù)指針包含 ARC 所需的那些細(xì)節(jié)(比如每個(gè) OC 方法調(diào)用都有的兩個(gè)隱藏參數(shù)self和_cmd)桌吃。這就是代碼第 4 行干的事(右邊的那個(gè)(void *)只是告訴編譯器朱沃,不用報(bào)類型強(qiáng)轉(zhuǎn)的 warning)。
更復(fù)雜的例子
如果 selector 接收參數(shù)茅诱,或者有返回值逗物,代碼就需要改改:
SEL selector =NSSelectorFromString(@"processRegion:ofView:");IMP imp = [_controller methodForSelector:selector];CGRect(*func)(id, SEL,CGRect,UIView*) = (void*)imp;CGRectresult = _controller ?? func(_controller, selector, someRect, someView) :CGRectZero;
為什么會(huì)有這個(gè) warning
原因是這樣的:我們?cè)?ARC 下調(diào)一個(gè)方法,runtime 需要知道對(duì)于返回值該怎么辦让簿。返回值可能有各種類型:void敬察,int,char尔当,NSString *莲祸,id等等。ARC 一般是根據(jù)返回值的頭文件來決定該怎么辦的椭迎,一共有以下 4 種情況:
直接忽略(如果是基本類型比如void锐帜,int這樣的)。
把返回值先 retain畜号,等到用不到的時(shí)候再 release(最常見的情況)缴阎。
不 retain,等到用不到的時(shí)候直接 release(用于init简软、copy這一類的方法蛮拔,或者標(biāo)注ns_returns_retained的方法)。
什么也不做痹升,默認(rèn)返回值在返回前后是始終有效的(一直到最近的 release pool 結(jié)束為止建炫,用于標(biāo)注ns_returns_autoreleased的方法)。
而調(diào)performSelector:的時(shí)候疼蛾,系統(tǒng)會(huì)默認(rèn)返回值并不是基本類型肛跌,但也不會(huì) retain、release察郁,也就是默認(rèn)采取第 4 種做法衍慎。所以如果那個(gè)方法本來應(yīng)該屬于前 3 種情況,都有可能會(huì)造成內(nèi)存泄漏皮钠。
對(duì)于返回void或者基本類型的方法稳捆,就目前而言你可以忽略這個(gè) warning,但這樣做不一定安全麦轰。我看過 Clang 在處理返回值這塊兒的幾次迭代演進(jìn)眷柔。一旦開著 ARC期虾,編譯器會(huì)覺得從performSelector:返回的對(duì)象沒理由不能 retain,不能 release驯嘱。在編譯器眼里镶苞,它就是個(gè)對(duì)象。所以鞠评,如果返回值是基本類型或者void茂蚓,編譯器還是存在會(huì) retain、release 它的可能剃幌,然后直接導(dǎo)致 crash聋涨。
帶參數(shù)調(diào)用
類似地,performSelector:withObject:也會(huì)報(bào)同一個(gè) warning负乡,因?yàn)椴恢该髟趺刺幚韰?shù)也會(huì)有同樣的問題牍白。ARC 允許為方法參數(shù)標(biāo)注consumed,如果你調(diào)的方法有這種標(biāo)注抖棘,最終可能導(dǎo)致把消息發(fā)給僵尸對(duì)象然后 crash茂腥。要解決這個(gè)問題可以用橋接(bridged casting),但是最好最簡(jiǎn)單的方法還是我上面寫的用IMP和函數(shù)指針的方法切省。不過給參數(shù)標(biāo) consumed 是比較少見的最岗,所以這個(gè)問題也不容易發(fā)生。
靜態(tài) selector
有趣的是朝捆,下面這種靜態(tài)聲明的 selector 就不會(huì)出 warning:
[_controller performSelector:@selector(someMethod)];
原因是般渡,這種情況下編譯器就能在編譯階段得到關(guān)于這個(gè) selector 的全部信息,不需要默認(rèn)任何事情
所有的 Objective-C 方法都有兩個(gè)隱藏的參數(shù)芙盘,self和_cmd驯用,調(diào)用時(shí)自動(dòng)加的。?
在 C 里調(diào)用NULL方法是不安全的儒老。而if (!_controller) { return; }這一句保證controller不為空蝴乔,所以我們一定能從methodForSelector:得到一個(gè)IMP(雖然可能只是_objc_msgForward,進(jìn)入消息轉(zhuǎn)發(fā)系統(tǒng))贷盲√哉猓基本上剥扣,有了這行檢查巩剖,就能保證我們有方法可調(diào)。?
實(shí)際上钠怯,如果返回值的類型是id佳魔,而你又沒 import 對(duì)應(yīng)的頭文件,它是有可能做出錯(cuò)誤處理的晦炊。有可能會(huì) crash 在一塊編譯器以為安全的代碼里鞠鲜。這種情況很罕見宁脊,但還是有發(fā)生的可能。一般來說贤姆,如果編譯器不知道該選哪個(gè)方法簽名榆苞,它會(huì)報(bào)一個(gè) warning 的。?
更多細(xì)節(jié)請(qǐng)參考 ARC 的文檔retain 返回值和不 retain 返回值霞捡。
2.使用宏忽略警告
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
在產(chǎn)生警告也就是 performSelector 的地方用使用該宏坐漏,如
SuppressPerformSelectorLeakWarning(
[_target performSelector:_action withObject:self]
);
如果需要 performSelector 返回值的話,
id result;
SuppressPerformSelectorLeakWarning(
result = [_target performSelector:_action withObject:self]
);