<h5>performSelector介紹</h5>
Objective-C本質(zhì)上是一門非常動(dòng)態(tài)的語言,NSObject定義了幾個(gè)方法,令開發(fā)者可以隨意調(diào)用任何方法。這幾個(gè)方法可以推遲執(zhí)行方法調(diào)用,也可以指定運(yùn)行方法所用的線程。這些功能在出現(xiàn)GCD之前非常有用。
- performSelector方法特點(diǎn)
這其中最簡單的是performSelector:(SEL)selector。該方法與直接調(diào)用選擇子等效。所以下面兩行代碼的執(zhí)行效果相同:
[object performSelector:@selector(selectorName)];
[object selectorName];
PerformSelector方法的區(qū)別在于,selector是在running time才決定的,這就是它的強(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];
- 弊端1:performSelector方法內(nèi)存管理容易有缺失
這種編程方式極為靈活,經(jīng)常可用來簡化復(fù)雜的代碼。還有一種用法,就是先把選擇子保存起來,等某個(gè)事件發(fā)生之后再調(diào)用。不管哪種用法,編譯器都不知道要執(zhí)行的選擇子是什么,者必須到了運(yùn)行期才能確定。然而,使用此特性的代價(jià)是,如果在ARC下編譯代碼,那么編譯器會(huì)發(fā)出如下警示信息:
warningL performSelector may casue a leak because its selector
is unknown [-Warc-performSelector-leaks]
你可能沒料到會(huì)出現(xiàn)這種警告。要是早就料到了,那么也許應(yīng)該已經(jīng)知道使用這些方法為何要小心了。這條消息看上去可能比較奇怪,而且令人納悶:為什么其中會(huì)提到內(nèi)存泄漏問題呢?只不過是用performSelector:調(diào)用了一個(gè)方法。原因在于,編譯器并不知道將要調(diào)用的selector是什么,因此,也就不了解其方法簽名及返回值,甚至連是否有返回值都不清楚。而且,由于編譯器不知道方法名,所以就沒辦法用ARC的內(nèi)存管理規(guī)則來判定返回值是不是該釋放。鑒于此,ARC采用了比較謹(jǐn)慎的做法,就是不添加釋放操作。然而,這么做可能導(dǎo)致內(nèi)存泄漏,因?yàn)榉椒ㄔ诜祷貙ο髸r(shí)已經(jīng)將其保留了。
這段話不是很容易懂,下面這段代碼應(yīng)該有助于理解
SEL selector;
if ( /* some condition */ ) {
selector = @selector(newObject);
// newObject返回一個(gè)new object
} else if ( /* some other condition */ ) {
selector = @selector(copy);
// copy根據(jù)當(dāng)前object copy出一個(gè)新的object
} else {
selector = @selector(someProperty));
// someProperty可以認(rèn)為是對象的某個(gè)property
}
id ret = [object performSelector:selector];
此代碼與剛才那個(gè)例子有所不同,以便展示問題所在,如果調(diào)用的是前兩個(gè)選擇子之一,那么ret對象應(yīng)由這段代碼來釋放,而如果是第三個(gè)選擇子,則無需釋放。如果不使用ARC(此時(shí)編譯器也不發(fā)出警告信息了),那么前兩種情況下需要手動(dòng)釋放ret對象,而后一種不需要釋放。如果使用ARC,則ARC應(yīng)該幫忙處理這些事情,但是目前來說ARC是很難解決這個(gè)問題的,正如上文所述,其采取的是謹(jǐn)慎的做法:不添加釋放操作,這就給程序帶來了內(nèi)存泄漏的可能。
顯然,這已然是performSelector的一大缺點(diǎn)(或說這是performSelector系列函數(shù)的一個(gè)坑吧)了。這個(gè)問題很容易被忽視,而且就算用靜態(tài)分析器,也很難偵測到隨后的內(nèi)存泄漏。
performSelector系列的方法之所以要謹(jǐn)慎使用,這就是其中一個(gè)原因。
- performSelector 弊端2:返回值只能是void或?qū)ο箢愋停╥d類型).
如果想返回整數(shù)或浮點(diǎn)數(shù)等scalar類型值,那么就需要執(zhí)行一些復(fù)雜的轉(zhuǎn)換操作,而這種轉(zhuǎn)換操作很容易出錯(cuò)。由于id類型表示指向任意Objective-C對象的指針,所以從技術(shù)上來講,只要返回的大小和指針?biāo)即笮∠嗤托校簿褪钦f,在32位架構(gòu)的計(jì)算機(jī)上,可以返回任意32位大小的類型;而在64位架構(gòu)的計(jì)算機(jī)上,則可以返回任意64位大小的類型。除此之外,還可以返回NSNumber進(jìn)行轉(zhuǎn)換…若返回的類型為C語言結(jié)構(gòu)體,則不可使用performSelector方法。
多用GCD,少用performSelector系列方法
performSelector系列方法中有某些方法可以被GCD代替。
- performSelector還有如下幾個(gè)版本,可以在發(fā)消息時(shí)順便傳遞參數(shù):
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
//示例:
//比方說,可以用下面這兩個(gè)版本來設(shè)置對象中名為value的屬性值:
id object = /* an object with a property called value */
id newValue = /* new value for the property */
[object performSelector:@selector(setValue:) withObject:newValue];
這些方法貌似有用,但是局限頗多!由于參數(shù)類型是id,所以傳入的參數(shù)必須是對象才行。如果選擇子所接受的參數(shù)是整數(shù)或浮點(diǎn)數(shù),那就不能采用這些方法了。此外,選擇子最多只能接受兩個(gè)參數(shù),也就是調(diào)用performSelector:withObject:withObject:這個(gè)版本。在參數(shù)不止兩個(gè)的情況下,則沒有對應(yīng)的performSelector方法能夠執(zhí)行這種選擇子。
- performSelector系列方法還有兩個(gè)功能,就是可以延后執(zhí)行選擇子,或?qū)⑦x擇子放在另一個(gè)線程上執(zhí)行。下面列出此方法中一些更為常用的版本:
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
當(dāng)然,這幾個(gè)方法還有一兩個(gè)別的變種,這里就略過了。然而,很快就會(huì)發(fā)現(xiàn),這些方法太過局限了。例如,具備延后功能的那些方法無法處理帶有兩個(gè)參數(shù)的選擇子。而能夠指定執(zhí)行線程的哪些方法,則與之類似,所以也不是特別通用。如果要用這些方法,就得把很多參數(shù)打包到字典中,然后在被調(diào)用的方法中將這些參數(shù)提取出來,這樣會(huì)增加開銷,同時(shí)也提高了產(chǎn)生bug的可能性。
如果改用替代方案GCD,那么就不受這些限制了。
performSelector系列方法所提供的線程功能,可以通過GCD機(jī)制中的塊來實(shí)現(xiàn);performSelector系列方法所提供的延后執(zhí)行功能,也可以用dispatch_after來實(shí)現(xiàn),在另一個(gè)線程上執(zhí)行任務(wù)則可通過dispatch_async和dispatch_sync來實(shí)現(xiàn)。
- GCD中延后執(zhí)行方案
例如,要延后執(zhí)行某項(xiàng)任務(wù),可以有下面兩種方式實(shí)現(xiàn),而我們應(yīng)該優(yōu)先考慮第二種:
//GCD延后執(zhí)行方案1:
// using performSelector:withObject:afterDelay:
[self performSelector:@selector(doSomething:) withObject:nil afterDelay:5.0]
//GCD延后執(zhí)行方案2:(推薦)
// using dispatch_after
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];
});
- GCD中主線程執(zhí)行任務(wù)方法
想把任務(wù)放在主線程上執(zhí)行,也可以有下面兩種方式,而我們還應(yīng)該優(yōu)先選擇后者:
//方案1:
// using performSelectorOnMainThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];
//方案2:(推薦)
// using dispatch_async
// (or if waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
總結(jié):
- performSelector系列方法在內(nèi)存管理上容易有缺失,它無法確定將要執(zhí)行的選擇子是什么,因而ARC編譯器也無法插入適當(dāng)?shù)膬?nèi)存管理方法,這是一個(gè)大坑,使用GCD則不存在這個(gè)問題。
- performSelector系列方法能處理的選擇子太過局限了,選擇子的返回值類型及發(fā)送給方法的參數(shù)個(gè)數(shù)都受到限制,不過GCD似乎也沒有比較好的解決方法(或許只是筆者不知道);
- 如果想把任務(wù)放在另外一個(gè)線程上執(zhí)行,或者想延時(shí)執(zhí)行某個(gè)任務(wù),最好應(yīng)該把任務(wù)封裝到block中,然后調(diào)用GCD的相關(guān)方法來實(shí)現(xiàn)。