42.多用GCD魏身,少用performSelector系列方法

《編寫高質(zhì)量iOS與OS X代碼的52個有效方法》--第六章 第42條
(ps:此乃讀書筆記惊橱,加深記憶,僅供大家參考)


第42條:多用GCD箭昵,少用performSelector系列方法

Objective-C本質(zhì)上是一門非常動態(tài)的語言(參見第11條)税朴,NSObject定義了幾個方法,令開發(fā)者可以隨意調(diào)用任何方法家制。這幾個方法可以推遲執(zhí)行方法調(diào)用正林,也可以指定運行方法所用的線程。這些功能原來很有用颤殴,但是在出現(xiàn)了大中樞派發(fā)及塊這樣的新技術之后觅廓,就顯得不那么必要了。雖說有些代碼還是會經(jīng)常用到它們涵但,但筆者勸你還是避開為妙杈绸。

這其中最簡單的是“performSelector:”。該方法與直接調(diào)用選擇子等效矮瘟。所以下面兩行代碼的執(zhí)行效果相同:

[self performSelector:@selector(selectorName)];
[self selectorName];

這種方式看上去似乎多余瞳脓。如果選擇子是在運行期決定的,那么就能體現(xiàn)出此方式的強大之處了澈侠。這就等于在動態(tài)綁定之上再次使用動態(tài)綁定劫侧,因而可以實現(xiàn)出下面這種功能:

SEL selector;
if (/* some condition */) {
    selector = @selector(foo);
}else if (/* some other condition */){
    selector = @selector(bar);
}else{
    selector = @selector(baz);
}
[object performSelector:selector];

這種編程方式極為靈活,經(jīng)成诳校可用來簡化復雜的代碼烧栋。還有一種用法,就是先把選擇子保存起來拳球,等某個事件發(fā)生之后再調(diào)用审姓。不管哪種用法,編譯器都不知道要執(zhí)行的選擇子是什么醇坝,這必須到了運行期才能確定邑跪。然而,使用此特性的代價是呼猪,如果在ARC下編譯代碼,那么編譯器會發(fā)出如下警示信息:

warning:PerformSelector may cause a leak because its selector is unknown

你可能沒料到會出現(xiàn)這種警告砸琅。這條消息看上去可能比較奇怪宋距,而且令人納悶:為什么其中會提到內(nèi)存泄漏問題呢?原因在于症脂,編譯器并不知道將要調(diào)用的選擇子是什么谚赎,因此也就不了解其方法簽名及返回值淫僻,甚至連是否有返回值都不清楚。而且壶唤,由于編譯器不知道方法名雳灵,所以就沒辦法運用ARC的內(nèi)存管理規(guī)則來判定返回值是不是應該釋放,鑒于此闸盔,ARC采用了比較謹慎的做法悯辙,就是不添加釋放操作。然而這么做可能導致內(nèi)存泄漏迎吵,因為方法在返回對象時 可能已經(jīng)將其保留了躲撰。

考慮下面這段代碼:

SEL selector;
if (/* some condition */) {
    selector = @selector(newObject);
}else if (/* some other condition */){
    selector = @selector(copy);
}else{
    selector = @selector(someProperty);
}
id ret = [object performSelector:selector];

如果調(diào)用的是兩個選擇子之一,那么ret對象應由這段代碼來釋放击费,如果是第三個選擇子拢蛋,則無須釋放。不僅在ARC環(huán)境下應該如此蔫巩,而且在非ARC環(huán)境下也應該這么做谆棱,這樣才算嚴格遵循了方法的命名規(guī)范。如果不使用ARC(此時編譯器也就不發(fā)警告信息了)圆仔,那么在前兩種情況下需要手動釋放ret對象础锐,而在后一種情況下則不需要釋放。這個問題很容易忽視荧缘,而且就算用靜態(tài)分析器皆警,也很難偵測到隨后的內(nèi)存泄漏。performSelector系列的方法之所以要謹慎使用截粗,這就是其中一個原因信姓。

這些方法不甚理想,另一個原因在于:返回值只能是void或?qū)ο箢愋统衤蕖1M管所要執(zhí)行的選擇子也可以返回void意推,但是performSelector方法的返回值類型畢竟是id。如果想返回整數(shù)或浮點數(shù)等類型的值珊蟀,那么就需要執(zhí)行一些復雜的轉(zhuǎn)換操作了菊值,而這種轉(zhuǎn)換很容易出錯。由于id類型表示指向任意Objective-C對象的指針育灸,所以從技術上來講腻窒,只要返回值的大小和指針所占大小相同就行。若返回值的類型為C語言的結構體磅崭,則不可使用performSelector方法儿子。

performSelector還有如下幾個版本,可以再發(fā)消息時順便傳遞參數(shù):

- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

這些方法貌似有用砸喻,但其實局限頗多柔逼。由于參數(shù)類型是id蒋譬,所以傳入的參數(shù)必須是對象才行。如果選擇子所接受的參數(shù)是整數(shù)或浮點數(shù)愉适,那就不能采用這些方法了犯助。此外,選擇子最多只能接受兩個參數(shù)维咸,而在參數(shù)不止兩個的情況下剂买,則沒有對應的performSelector方法能夠執(zhí)行此種選擇子。

performSelector系列方法還有個功能腰湾,就是可以延后執(zhí)行選擇子雷恃,或?qū)⑵浞旁诹硪粋€線程上執(zhí)行。

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

然而很快就會發(fā)現(xiàn)费坊,這些方法太過局限了倒槐。如果要用這些方法,就得把許多參數(shù)都打包到字典中附井,然后在受調(diào)用的方法里將其提取出來讨越,這樣會增加開銷,而且還可能出bug永毅。

如果改用其他替代方案把跨,那就不受這些限制了。最主要的替代方案就是使用塊(參見第37條)沼死。而且着逐,performSelector系列方法所提供的線程功能,都可以通過在大中樞派發(fā)機制中使用塊來實現(xiàn)意蛀。延后執(zhí)行可以用dispatch_after來實現(xiàn)耸别,在另一個線程上執(zhí)行任務則可通過dispatch_sync及dispatch_async來實現(xiàn)。

例如县钥,延后執(zhí)行某項任務:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));

dispatch_after(time, dispatch_get_main_queue(), ^{
    [self doSomething];
});

想把任務放在主線程上執(zhí)行:

dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

要點

  • performSelector系列方法在內(nèi)存管理方面容易有疏失秀姐。它無法確定將要執(zhí)行的選擇子具體是什么,因而ARC編譯器也就無法插入適當?shù)膬?nèi)存管理方法若贮。
  • performSelector系列方法所能處理的選擇子太過局限了省有,選擇子的返回值類型及發(fā)送給方法的參數(shù)個數(shù)都受到限制。
  • 如果想把任務放在另一個線程上執(zhí)行谴麦,那么最好不要用performSelector系列方法蠢沿,而是應該把任務封裝到塊里,然后調(diào)用大中樞派發(fā)機制的相關方法來實現(xiàn)细移。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搏予,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子弧轧,更是在濱河造成了極大的恐慌雪侥,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件精绎,死亡現(xiàn)場離奇詭異速缨,居然都是意外死亡,警方通過查閱死者的電腦和手機代乃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門旬牲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搁吓,你說我怎么就攤上這事原茅。” “怎么了堕仔?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵擂橘,是天一觀的道長。 經(jīng)常有香客問我摩骨,道長通贞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任恼五,我火速辦了婚禮昌罩,結果婚禮上,老公的妹妹穿的比我還像新娘灾馒。我一直安慰自己茎用,他們只是感情好,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布睬罗。 她就那樣靜靜地躺著轨功,像睡著了一般。 火紅的嫁衣襯著肌膚如雪傅物。 梳的紋絲不亂的頭發(fā)上夯辖,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音董饰,去河邊找鬼蒿褂。 笑死,一個胖子當著我的面吹牛卒暂,可吹牛的內(nèi)容都是我干的啄栓。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼也祠,長吁一口氣:“原來是場噩夢啊……” “哼昙楚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诈嘿,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤堪旧,失蹤者是張志新(化名)和其女友劉穎削葱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淳梦,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡析砸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了爆袍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片首繁。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖陨囊,靈堂內(nèi)的尸體忽然破棺而出弦疮,到底是詐尸還是另有隱情,我是刑警寧澤蜘醋,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布胁塞,位于F島的核電站,受9級特大地震影響堂湖,放射性物質(zhì)發(fā)生泄漏闲先。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一无蜂、第九天 我趴在偏房一處隱蔽的房頂上張望伺糠。 院中可真熱鬧,春花似錦斥季、人聲如沸训桶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舵揭。三九已至,卻和暖如春躁锡,著一層夾襖步出監(jiān)牢的瞬間午绳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工映之, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拦焚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓杠输,卻偏偏與公主長得像赎败,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蠢甲,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

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