42: 多用 GCD,少用 PERFORMSELECTOR 系列方法

<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)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徊件,一起剝皮案震驚了整個(gè)濱河市奸攻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虱痕,老刑警劉巖睹耐,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異皆疹,居然都是意外死亡疏橄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門略就,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捎迫,“玉大人,你說我怎么就攤上這事表牢≌蓿” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵崔兴,是天一觀的道長(zhǎng)彰导。 經(jīng)常有香客問我,道長(zhǎng)敲茄,這世上最難降的妖魔是什么位谋? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮堰燎,結(jié)果婚禮上掏父,老公的妹妹穿的比我還像新娘。我一直安慰自己秆剪,他們只是感情好赊淑,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仅讽,像睡著了一般陶缺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洁灵,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天饱岸,我揣著相機(jī)與錄音,去河邊找鬼。 笑死伶贰,一個(gè)胖子當(dāng)著我的面吹牛蛛砰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播黍衙,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼泥畅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了琅翻?” 一聲冷哼從身側(cè)響起位仁,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎方椎,沒想到半個(gè)月后聂抢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棠众,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年琳疏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闸拿。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡空盼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出新荤,到底是詐尸還是另有隱情揽趾,我是刑警寧澤,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布苛骨,位于F島的核電站篱瞎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏痒芝。R本人自食惡果不足惜俐筋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望严衬。 院中可真熱鬧校哎,春花似錦、人聲如沸瞳步。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽单起。三九已至,卻和暖如春劣坊,著一層夾襖步出監(jiān)牢的瞬間嘀倒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留测蘑,地道東北人灌危。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像碳胳,于是被迫代替她去往敵國(guó)和親勇蝙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

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