[爆棧熱門 iOS 問題] performSelector may cause a leak because its selector is unknown

系列文集:爆棧熱門 iOS 問題渐扮。目錄在此饥悴。倉薯翻譯送漠,歡迎指正:)

問題

我在 ARC 模式下編譯出了這個 warning:

"performSelector may cause a leak because its selector is unknown".

我的代碼是這么寫的:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

為什么會有這個 warning 呢粉渠?我知道編譯器無法檢查實際上有沒有這個 selector,不過這為什么會造成內(nèi)存泄漏呢派哲?代碼應(yīng)該怎么改才能消除這個 warning?


答案

答案1:單純消除 warning

Scott Thompson掺喻,1100 票

LLVM 3.0 編譯器可以用以下代碼消除 warning:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

如果在多個地方都要用芭届,可以定義一個宏:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

用的時候:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

如果需要返回值:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

答案2:詳細(xì)解釋和正統(tǒng)解決

wbyoung,768 贊

解決方案

編譯器報這個 warning 是有原因的巢寡,一般不應(yīng)該直接忽略喉脖,而且消除這個 warning 并不難。如下即可:

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

或者寫得緊密一些(不過可讀性差一些抑月,也少了類型檢查):

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

代碼解釋

這一堆代碼在做的事情其實是树叽,向 controller 請求那個方法對應(yīng)的 C 函數(shù)指針。所有的NSObject都能響應(yīng)methodForSelector:這個方法谦絮,不過也可以用 Objective-C runtime 里的class_getMethodImplementation(只在 protocol 的情況下有用题诵,id<SomeProto>這樣的)。這種函數(shù)指針叫做IMP层皱,就是typedef過的函數(shù)指針(id (*IMP)(id, SEL, ...)[1])性锭。它跟方法簽名(signature)比較像,雖然可能不是完全一樣叫胖。

得到IMP之后草冈,還需要進(jìn)行轉(zhuǎn)換,轉(zhuǎn)換后的函數(shù)指針包含 ARC 所需的那些細(xì)節(jié)(比如每個 OC 方法調(diào)用都有的兩個隱藏參數(shù)self_cmd)。這就是代碼第 4 行干的事(右邊的那個(void *)只是告訴編譯器怎棱,不用報類型強(qiáng)轉(zhuǎn)的 warning)哩俭。

最后一步,調(diào)用函數(shù)指針[2]拳恋。

更復(fù)雜的例子

如果 selector 接收參數(shù)凡资,或者有返回值,代碼就需要改改:

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;

為什么會有這個 warning

原因是這樣的:我們在 ARC 下調(diào)一個方法谬运,runtime 需要知道對于返回值該怎么辦隙赁。返回值可能有各種類型:voidint梆暖,char伞访,NSString *id等等式廷。ARC 一般是根據(jù)返回值的頭文件來決定該怎么辦的[3]咐扭,一共有以下 4 種情況[4]

  1. 直接忽略(如果是基本類型比如 voidint這樣的)滑废。
  2. 把返回值先 retain蝗肪,等到用不到的時候再 release(最常見的情況)。
  3. 不 retain蠕趁,等到用不到的時候直接 release(用于 init薛闪、copy 這一類的方法,或者標(biāo)注ns_returns_retained的方法)俺陋。
  4. 什么也不做豁延,默認(rèn)返回值在返回前后是始終有效的(一直到最近的 release pool 結(jié)束為止,用于標(biāo)注ns_returns_autoreleased的方法)腊状。

而調(diào)performSelector:的時候诱咏,系統(tǒng)會默認(rèn)返回值并不是基本類型,但也不會 retain缴挖、release袋狞,也就是默認(rèn)采取第 4 種做法。所以如果那個方法本來應(yīng)該屬于前 3 種情況映屋,都有可能會造成內(nèi)存泄漏苟鸯。

對于返回void或者基本類型的方法,就目前而言你可以忽略這個 warning棚点,但這樣做不一定安全早处。我看過 Clang 在處理返回值這塊兒的幾次迭代演進(jìn)。一旦開著 ARC瘫析,編譯器會覺得從performSelector:返回的對象沒理由不能 retain砌梆,不能 release默责。在編譯器眼里,它就是個對象么库。所以傻丝,如果返回值是基本類型或者void甘有,編譯器還是存在會 retain诉儒、release 它的可能,然后直接導(dǎo)致 crash亏掀。

帶參數(shù)調(diào)用

類似地忱反,performSelector:withObject:也會報同一個 warning,因為不指明怎么處理參數(shù)也會有同樣的問題滤愕。ARC 允許為方法參數(shù)標(biāo)注consumed温算,如果你調(diào)的方法有這種標(biāo)注,最終可能導(dǎo)致把消息發(fā)給僵尸對象然后 crash间影。要解決這個問題可以用橋接(bridged casting)注竿,但是最好最簡單的方法還是我上面寫的用IMP和函數(shù)指針的方法。不過給參數(shù)標(biāo) consumed 是比較少見的魂贬,所以這個問題也不容易發(fā)生巩割。

靜態(tài) selector

有趣的是,下面這種靜態(tài)聲明的 selector 就不會出 warning:

[_controller performSelector:@selector(someMethod)];

原因是付燥,這種情況下編譯器就能在編譯階段得到關(guān)于這個 selector 的全部信息宣谈,不需要默認(rèn)任何事情。

倉薯注:后面作者還寫了點歷史键科,我就沒有翻譯了闻丑,感興趣請前往原文閱讀。


原文地址:performSelector may cause a leak because its selector is unknown

本文地址:http://www.reibang.com/p/6517ab655be7

系列文集:爆棧熱門 iOS 問題

譯者:@戴倉薯


  1. 所有的 Objective-C 方法都有兩個隱藏的參數(shù)勋颖,self_cmd嗦嗡,調(diào)用時自動加的。 ?

  2. 在 C 里調(diào)用NULL方法是不安全的饭玲。而if (!_controller) { return; }這一句保證controller不為空侥祭,所以我們一定能從methodForSelector:得到一個IMP(雖然可能只是_objc_msgForward,進(jìn)入消息轉(zhuǎn)發(fā)系統(tǒng))咱枉”傲颍基本上,有了這行檢查蚕断,就能保證我們有方法可調(diào)欢伏。 ?

  3. 實際上,如果返回值的類型是id亿乳,而你又沒 import 對應(yīng)的頭文件硝拧,它是有可能做出錯誤處理的径筏。有可能會 crash 在一塊編譯器以為安全的代碼里。這種情況很罕見障陶,但還是有發(fā)生的可能滋恬。一般來說,如果編譯器不知道該選哪個方法簽名抱究,它會報一個 warning 的恢氯。 ?

  4. 更多細(xì)節(jié)請參考 ARC 的文檔 retain 返回值不 retain 返回值?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鼓寺,一起剝皮案震驚了整個濱河市勋拟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妈候,老刑警劉巖敢靡,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異苦银,居然都是意外死亡啸胧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門幔虏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纺念,“玉大人,你說我怎么就攤上這事所计∧牵” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵主胧,是天一觀的道長叭首。 經(jīng)常有香客問我,道長踪栋,這世上最難降的妖魔是什么焙格? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任泪姨,我火速辦了婚禮健田,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旅敷。我一直安慰自己囤官,他們只是感情好冬阳,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著党饮,像睡著了一般肝陪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刑顺,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天氯窍,我揣著相機(jī)與錄音饲常,去河邊找鬼。 笑死狼讨,一個胖子當(dāng)著我的面吹牛贝淤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播政供,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼播聪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鲫骗?” 一聲冷哼從身側(cè)響起犬耻,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎执泰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渡蜻,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡术吝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茸苇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片排苍。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖学密,靈堂內(nèi)的尸體忽然破棺而出淘衙,到底是詐尸還是另有隱情,我是刑警寧澤腻暮,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布彤守,位于F島的核電站,受9級特大地震影響哭靖,放射性物質(zhì)發(fā)生泄漏具垫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一试幽、第九天 我趴在偏房一處隱蔽的房頂上張望筝蚕。 院中可真熱鬧,春花似錦铺坞、人聲如沸起宽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坯沪。三九已至,卻和暖如春腿短,著一層夾襖步出監(jiān)牢的瞬間屏箍,已是汗流浹背绘梦。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留赴魁,地道東北人卸奉。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像颖御,于是被迫代替她去往敵國和親榄棵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉潘拱,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • 問題描述 項目中使用到了從字符串創(chuàng)建選擇器疹鳄,編譯時發(fā)現(xiàn)警告:"performSelector may cause ...
    ttdiOS閱讀 555評論 0 1
  • __block和__weak修飾符的區(qū)別其實是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用,...
    LZM輪回閱讀 3,284評論 0 6
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,125評論 29 470
  • 火車站,多的是一些離別的人和故事禽最。記得大學(xué)畢業(yè)那年腺怯,我的大學(xué)閨蜜kk到火車站送我。本以為會上演一幕生離死別川无、淚水漣...
    七蕭蕭閱讀 458評論 0 1