<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)
這其中最簡(jiǎ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)常可用來簡(jiǎn)化復(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)榉椒ㄔ诜祷貙?duì)象時(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)為是對(duì)象的某個(gè)property
}
id ret = [object performSelector:selector];
此代碼與剛才那個(gè)例子有所不同珠漂,以便展示問題所在,如果調(diào)用的是前兩個(gè)選擇子之一尾膊,那么ret對(duì)象應(yīng)由這段代碼來釋放媳危,而如果是第三個(gè)選擇子,則無需釋放冈敛。如果不使用ARC(此時(shí)編譯器也不發(fā)出警告信息了)待笑,那么前兩種情況下需要手動(dòng)釋放ret對(duì)象,而后一種不需要釋放抓谴。如果使用ARC暮蹂,則ARC應(yīng)該幫忙處理這些事情寞缝,但是目前來說ARC是很難解決這個(gè)問題的,正如上文所述仰泻,其采取的是謹(jǐn)慎的做法:不添加釋放操作荆陆,這就給程序帶來了內(nèi)存泄漏的可能。
顯然我纪,這已然是performSelector的一大缺點(diǎn)(或說這是performSelector系列函數(shù)的一個(gè)坑吧)了慎宾。這個(gè)問題很容易被忽視,而且就算用靜態(tài)分析器浅悉,也很難偵測(cè)到隨后的內(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對(duì)象的指針咳促,所以從技術(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è)置對(duì)象中名為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ù)必須是對(duì)象才行兵志。如果選擇子所接受的參數(shù)是整數(shù)或浮點(diǎn)數(shù),那就不能采用這些方法了宣肚。此外想罕,選擇子最多只能接受兩個(gè)參數(shù),也就是調(diào)用performSelector:withObject:withObject:這個(gè)版本。在參數(shù)不止兩個(gè)的情況下按价,則沒有對(duì)應(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)。