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

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


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

Objective-C本質(zhì)上是一門(mén)非常動(dòng)態(tài)的語(yǔ)言(參見(jiàn)第11條),NSObject定義了幾個(gè)方法,令開(kāi)發(fā)者可以隨意調(diào)用任何方法。這幾個(gè)方法可以推遲執(zhí)行方法調(diào)用,也可以指定運(yùn)行方法所用的線程。這些功能原來(lái)很有用,但是在出現(xiàn)了大中樞派發(fā)及塊這樣的新技術(shù)之后,就顯得不那么必要了。雖說(shuō)有些代碼還是會(huì)經(jīng)常用到它們,但筆者勸你還是避開(kāi)為妙。

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

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

這種方式看上去似乎多余。如果選擇子是在運(yùn)行期決定的,那么就能體現(xiàn)出此方式的強(qiáng)大之處了。這就等于在動(dòng)態(tài)綁定之上再次使用動(dòng)態(tài)綁定,因而可以實(shí)現(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)常可用來(lái)簡(jiǎn)化復(fù)雜的代碼。還有一種用法,就是先把選擇子保存起來(lái),等某個(gè)事件發(fā)生之后再調(diào)用。不管哪種用法,編譯器都不知道要執(zhí)行的選擇子是什么,這必須到了運(yùn)行期才能確定。然而,使用此特性的代價(jià)是,如果在ARC下編譯代碼,那么編譯器會(huì)發(fā)出如下警示信息:

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

你可能沒(méi)料到會(huì)出現(xiàn)這種警告。這條消息看上去可能比較奇怪,而且令人納悶:為什么其中會(huì)提到內(nèi)存泄漏問(wèn)題呢?原因在于,編譯器并不知道將要調(diào)用的選擇子是什么,因此也就不了解其方法簽名及返回值,甚至連是否有返回值都不清楚。而且,由于編譯器不知道方法名,所以就沒(méi)辦法運(yùn)用ARC的內(nèi)存管理規(guī)則來(lái)判定返回值是不是應(yīng)該釋放,鑒于此,ARC采用了比較謹(jǐn)慎的做法,就是不添加釋放操作。然而這么做可能導(dǎo)致內(nèi)存泄漏,因?yàn)榉椒ㄔ诜祷貙?duì)象時(shí) 可能已經(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)用的是兩個(gè)選擇子之一,那么ret對(duì)象應(yīng)由這段代碼來(lái)釋放,如果是第三個(gè)選擇子,則無(wú)須釋放。不僅在ARC環(huán)境下應(yīng)該如此,而且在非ARC環(huán)境下也應(yīng)該這么做,這樣才算嚴(yán)格遵循了方法的命名規(guī)范。如果不使用ARC(此時(shí)編譯器也就不發(fā)警告信息了),那么在前兩種情況下需要手動(dòng)釋放ret對(duì)象,而在后一種情況下則不需要釋放。這個(gè)問(wèn)題很容易忽視,而且就算用靜態(tài)分析器,也很難偵測(cè)到隨后的內(nèi)存泄漏。performSelector系列的方法之所以要謹(jǐn)慎使用,這就是其中一個(gè)原因。

這些方法不甚理想,另一個(gè)原因在于:返回值只能是void或?qū)ο箢?lèi)型。盡管所要執(zhí)行的選擇子也可以返回void,但是performSelector方法的返回值類(lèi)型畢竟是id。如果想返回整數(shù)或浮點(diǎn)數(shù)等類(lèi)型的值,那么就需要執(zhí)行一些復(fù)雜的轉(zhuǎn)換操作了,而這種轉(zhuǎn)換很容易出錯(cuò)。由于id類(lèi)型表示指向任意Objective-C對(duì)象的指針,所以從技術(shù)上來(lái)講,只要返回值的大小和指針?biāo)即笮∠嗤托小H舴祷刂档念?lèi)型為C語(yǔ)言的結(jié)構(gòu)體,則不可使用performSelector方法。

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

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

這些方法貌似有用,但其實(shí)局限頗多。由于參數(shù)類(lèi)型是id,所以傳入的參數(shù)必須是對(duì)象才行。如果選擇子所接受的參數(shù)是整數(shù)或浮點(diǎn)數(shù),那就不能采用這些方法了。此外,選擇子最多只能接受兩個(gè)參數(shù),而在參數(shù)不止兩個(gè)的情況下,則沒(méi)有對(duì)應(yīng)的performSelector方法能夠執(zhí)行此種選擇子。

performSelector系列方法還有個(gè)功能,就是可以延后執(zhí)行選擇子,或?qū)⑵浞旁诹硪粋€(gè)線程上執(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);

然而很快就會(huì)發(fā)現(xiàn),這些方法太過(guò)局限了。如果要用這些方法,就得把許多參數(shù)都打包到字典中,然后在受調(diào)用的方法里將其提取出來(lái),這樣會(huì)增加開(kāi)銷(xiāo),而且還可能出bug。

如果改用其他替代方案,那就不受這些限制了。最主要的替代方案就是使用塊(參見(jiàn)第37條)。而且,performSelector系列方法所提供的線程功能,都可以通過(guò)在大中樞派發(fā)機(jī)制中使用塊來(lái)實(shí)現(xiàn)。延后執(zhí)行可以用dispatch_after來(lái)實(shí)現(xiàn),在另一個(gè)線程上執(zhí)行任務(wù)則可通過(guò)dispatch_sync及dispatch_async來(lái)實(shí)現(xiàn)。

例如,延后執(zhí)行某項(xiàng)任務(wù):

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];
});

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

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

要點(diǎn)

  • performSelector系列方法在內(nèi)存管理方面容易有疏失。它無(wú)法確定將要執(zhí)行的選擇子具體是什么,因而ARC編譯器也就無(wú)法插入適當(dāng)?shù)膬?nèi)存管理方法。
  • performSelector系列方法所能處理的選擇子太過(guò)局限了,選擇子的返回值類(lèi)型及發(fā)送給方法的參數(shù)個(gè)數(shù)都受到限制。
  • 如果想把任務(wù)放在另一個(gè)線程上執(zhí)行,那么最好不要用performSelector系列方法,而是應(yīng)該把任務(wù)封裝到塊里,然后調(diào)用大中樞派發(fā)機(jī)制的相關(guān)方法來(lái)實(shí)現(xiàn)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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