performSelector

消息處理之performSelector
[爆棧熱門 iOS 問題] performSelector may cause a leak because its selector is unknown

引入

怎樣使用performSelector傳入3個(gè)以上參數(shù)叠赦,其中一個(gè)為結(jié)構(gòu)體?

一幕帆、performSelector 和 直接調(diào)用方法 的區(qū)別

performSelector: withObject:是在iOS中的一種方法調(diào)用方式。他可以向一個(gè)對(duì)象傳遞任何消息虑粥,而不需要在編譯的時(shí)候聲明這些方法如孝。所以這也是runtime的一種應(yīng)用方式。

所以performSelector和直接調(diào)用方法的區(qū)別就在與runtime娩贷。直接調(diào)用編譯是會(huì)自動(dòng)校驗(yàn)第晰。如果方法不存在,那么直接調(diào)用 在編譯時(shí)候就能夠發(fā)現(xiàn)彬祖,編譯器會(huì)直接報(bào)錯(cuò)茁瘦。
但是使用performSelector的話一定是在運(yùn)行時(shí)候才能發(fā)現(xiàn),如果此方法不存在就會(huì)崩潰储笑。所以如果使用performSelector的話他就會(huì)有個(gè)最佳伴侶- (BOOL)respondsToSelector:(SEL)aSelector;來在運(yùn)行時(shí)判斷對(duì)象是否響應(yīng)此方法甜熔。

直接調(diào)用方法時(shí)候,一定要在頭文件中聲明該方法的使用突倍,也要將頭文件import進(jìn)來腔稀。而使用performSelector時(shí)候,可以不用import頭文件包含方法的對(duì)象羽历,直接用performSelector調(diào)用即可焊虏。

二、常用的performSelector簡單分析

1.

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

這三個(gè)方法秕磷,均為同步執(zhí)行诵闭,與線程無關(guān),主線程和子線程中均可調(diào)用成功澎嚣。等同于直接調(diào)用該方法疏尿。在需要?jiǎng)討B(tài)的去調(diào)用方法的時(shí)候去使用。
例如:[self performSelector:@selector(test2)];與[self test2];執(zhí)行效果上完全相同易桃。

2.

  • (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
  • (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

這兩個(gè)方法為異步執(zhí)行褥琐,即使delay傳參為0,仍為異步執(zhí)行颈抚。只能在主線程中執(zhí)行踩衩,在子線程中不會(huì)調(diào)到aSelector方法》泛海可用于當(dāng)點(diǎn)擊UI中一個(gè)按鈕會(huì)觸發(fā)一個(gè)消耗系統(tǒng)性能的事件,在事件執(zhí)行期間按鈕會(huì)一直處于高亮狀態(tài)锚赤,此時(shí)可以調(diào)用該方法去異步的處理該事件匹舞,就能避免上面的問題。
在方法未到執(zhí)行時(shí)間之前线脚,取消方法為:

  • (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
  • (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

注意:調(diào)用該方法之前或在該方法所在的viewController生命周期結(jié)束的時(shí)候去調(diào)用取消函數(shù)赐稽,以確保不會(huì)引起內(nèi)存泄露叫榕。

  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

這兩個(gè)方法,在主線程和子線程中均可執(zhí)行姊舵,均會(huì)調(diào)用主線程的aSelector方法晰绎;
如果設(shè)置wait為YES:等待當(dāng)前線程執(zhí)行完以后,主線程才會(huì)執(zhí)行aSelector方法括丁;
設(shè)置為NO:不等待當(dāng)前線程執(zhí)行完荞下,就在主線程上執(zhí)行aSelector方法。
如果史飞,當(dāng)前線程就是主線程尖昏,那么aSelector方法會(huì)馬上執(zhí)行。
注意:apple不允許程序員在主線程以外的線程中對(duì)ui進(jìn)行操作构资,此時(shí)我們必須調(diào)用performSelectorOnMainThread函數(shù)在主線程中完成UI的更新抽诉。

4.

  • (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
  • (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

調(diào)用指定線程中的某個(gè)方法。分析效果同3吐绵。

5.

  • (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

開啟子線程在后臺(tái)運(yùn)行

三迹淌、performSelector的簡單使用

第一種:無參數(shù)傳遞

[self performSelector:@selector(SelectorNoParameter)];
- (void)SelectorNoParameter{
    
    NSLog(@"SelectorNoParameter");
}

第二種:傳遞一個(gè)參數(shù)

[self performSelector:@selector(SelectorOneParameter:) withObject:@"firstParameter"];
- (void)SelectorOneParameter:(NSString *)first{
    
    NSLog(@"Logs: %@", first);
}

第三種:傳遞兩個(gè)參數(shù)

[self performSelector:@selector(SelectorFirstParameter:SecondParameter:) withObject:@"firstParameter" withObject:@"secondParameter"];
- (void)SelectorFirstParameter:(NSString *)first SecondParameter:(NSString *)second{
    
    NSLog(@"Logs %@ %@", first, second);
}

第四種:建立動(dòng)態(tài)的函數(shù),然后調(diào)用它們

NSArray *objectArray = @[@{@"methodName":@"DynamicParameterString:",@"value":@"String"},@{@"methodName":@"DynamicParameterNumber:",@"value":@2}];
for (NSDictionary *dic in objectArray) {
        
    [self performSelector:NSSelectorFromString([dic objectForKey:@"methodName"]) withObject:[dic objectForKey:@"value"]];
}
- (void)DynamicParameterString:(NSString *)string{
    
    NSLog(@"DynamicParameterString: %@",string);
}

- (void)DynamicParameterNumber:(NSNumber *)number{
    
    NSLog(@"DynamicParameterNumber: %@",number);
}

四己单、performSelector相關(guān)的應(yīng)用

3.1巍沙、performSelector如何傳遞三個(gè)及以上的參數(shù)
  • 一種是使用NSInvocation,
  • 一種是把多個(gè)參數(shù)封裝成一個(gè)參數(shù)荷鼠。
  • 第三種是使用objc_msgSend句携,應(yīng)該不算用performSelector這種方式,但是performSelector最后也還是用objc_msgSend這個(gè)方法進(jìn)行消息轉(zhuǎn)發(fā)的允乐,所以姑且也寫進(jìn)來了矮嫉。

第一種使用了runtime的反射機(jī)制,效率較低牍疏,可讀性不高蠢笋。第二種可讀性高,效率稍微高點(diǎn)鳞陨。


第一種:NSInvocation
- (id)performSelector:(SEL)selector withObjects:(NSArray *)objects
{
    // 方法簽名(方法的描述)
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
    if (signature == nil) {
        
        //可以拋出異常也可以不操作昨寞。
    }
    
    // NSInvocation : 利用一個(gè)NSInvocation對(duì)象包裝一次方法調(diào)用(方法調(diào)用者、方法名厦滤、方法參數(shù)援岩、方法返回值)
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = selector;
    
    // 設(shè)置參數(shù)
    NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的參數(shù)個(gè)數(shù)
    paramsCount = MIN(paramsCount, objects.count);
    for (NSInteger i = 0; i < paramsCount; i++) {
        id object = objects[i];
        if ([object isKindOfClass:[NSNull class]]) continue;
        [invocation setArgument:&object atIndex:i + 2];
    }
    
    // 調(diào)用方法
    [invocation invoke];
    
    // 獲取返回值
    id returnValue = nil;
    if (signature.methodReturnLength) { // 有返回值類型掏导,才去獲得返回值
        [invocation getReturnValue:&returnValue];
    }
    
    return returnValue;
}
NSString *str = @"字符串";
NSNumber *num = @20;
NSArray *arr = @[@"數(shù)組值1", @"數(shù)組值2"];
SEL sel = NSSelectorFromString(@"NSInvocationWithString:withNum:withArray:");
NSArray *objs = [NSArray arrayWithObjects:str, num, arr, nil];
    
[self performSelector:sel withObjects:objs];
- (void)NSInvocationWithString:(NSString *)string withNum:(NSNumber *)number withArray:(NSArray *)array {
    NSLog(@"%@, %@, %@", string, number, array[0]);
}
第二種:把多個(gè)參數(shù)封裝成一個(gè)參數(shù)

比如可以把多個(gè)參數(shù)封裝成NSDictionary享怀,然后進(jìn)行傳遞。

第三種:objc_msgSend
NSString *str = @"字符串objc_msgSend";
NSNumber *num = @20;
NSArray *arr = @[@"數(shù)組值1", @"數(shù)組值2"];
SEL sel = NSSelectorFromString(@"ObjcMsgSendWithString:withNum:withArray:");
((void (*) (id, SEL, NSString *, NSNumber *, NSArray *)) objc_msgSend) (self, sel, str, num, arr);
- (void)ObjcMsgSendWithString:(NSString *)string withNum:(NSNumber *)number withArray:(NSArray *)array {
    NSLog(@"%@, %@, %@", string, number, array[0]);
}
3.2趟咆、參數(shù)中有結(jié)構(gòu)體

如果傳入performSelector中的多個(gè)參數(shù)有結(jié)構(gòu)體怎么辦添瓷?

可以把結(jié)構(gòu)體轉(zhuǎn)換為對(duì)象梅屉。

typedef struct ParameterStruct{
    int a;
    int b;
}MyStruct;
NSString *str = @"字符串 把結(jié)構(gòu)體轉(zhuǎn)換為對(duì)象";
NSNumber *num = @20;
NSArray *arr = @[@"數(shù)組值1", @"數(shù)組值2"];
    
MyStruct mystruct = {10,20};
NSValue *value = [NSValue valueWithBytes:&mystruct objCType:@encode(MyStruct)];
    
SEL sel = NSSelectorFromString(@"NSInvocationWithString:withNum:withArray:withValue:");
NSArray *objs = [NSArray arrayWithObjects:str, num, arr, value,nil];
    
[self performSelector:sel withObjects:objs];
- (void)NSInvocationWithString:(NSString *)string withNum:(NSNumber *)number withArray:(NSArray *)array withValue:(NSValue *)value{
    
    MyStruct struceBack;
    [value getValue:&struceBack];
    
    NSLog(@"%@, %@, %@, %d", string, number, array[0],struceBack.a);
}
3.3、aSelector方法被延遲調(diào)用
3.4鳞贷、在子線程中無法調(diào)用selector方法

在子線程中無法調(diào)用selector方法這種情況是只有使用以下方法的時(shí)候才出現(xiàn):

- (void)performSelector:(SEL)aSelector withObject:(id)arg afterDelay:(NSTimeInterval)delay;
這是為什么呢坯汤?原因如下:

1次洼、afterDelay方式是使用當(dāng)前線程的RunLoop中根據(jù)afterDelay參數(shù)創(chuàng)建一個(gè)Timer定時(shí)器在一定時(shí)間后調(diào)用SEL震嫉,NO AfterDelay方式是直接調(diào)用SEL谣蠢。

2必盖、子線程中默認(rèn)是沒有啟動(dòng)runloop的瑟由,需要手動(dòng)創(chuàng)建厦凤,只要調(diào)用獲取當(dāng)前線程RunLoop方法即可創(chuàng)建富蓄。

解決方法有兩種:
  • 創(chuàng)建子線程的runloop
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self performSelector:@selector(delayMethod) withObject:nil afterDelay:0];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"調(diào)用方法==開始");
    sleep(5);
    NSLog(@"調(diào)用方法==結(jié)束");
});
  • 使用dispatch_after在子線程上執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
        if ([self respondsToSelector:@selector(delayMethod)]) {
            [self performSelector:@selector(delayMethod) withObject:nil];
        }
});
    
NSLog(@"調(diào)用方法==開始");
sleep(5);
NSLog(@"調(diào)用方法==結(jié)束");
3.5训挡、防止按鈕多次點(diǎn)擊

這種方式是在0.2秒內(nèi)取消之前的點(diǎn)擊事件眷蚓,以做到防止多次點(diǎn)擊鼻种。

-(void)completeClicked:(UIButton *)sender{
    [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClick:) object:sender];
    [self performSelector:@selector(buttonClick:) withObject:sender afterDelay:0.2f];
}

這種方式是在點(diǎn)擊后設(shè)為不可被點(diǎn)擊的狀態(tài),1秒后恢復(fù)

-(void)buttonClicked:(id)sender{

    self.button.enabled =NO;

    [selfperformSelector:@selector(changeButtonStatus)withObject:nilafterDelay:1.0f];//防止重復(fù)點(diǎn)擊
}

-(void)changeButtonStatus{

    self.button.enabled =YES;
}

五沙热、用performSelector的時(shí)候要注意別內(nèi)存泄露了

performSelector延時(shí)調(diào)用的問題叉钥,

performSelector關(guān)于內(nèi)存管理的執(zhí)行原理是這樣的:執(zhí)行 [self performSelector:@selector(method1:) withObject:self afterDelay:3];的時(shí)候,系統(tǒng)會(huì)將self的引用計(jì)數(shù)加1篙贸,執(zhí)行完這個(gè)方法時(shí)投队,還會(huì)將self的引用計(jì)數(shù)減1,當(dāng)方法還沒有執(zhí)行的時(shí)候爵川,要返回父視圖釋放當(dāng)前視圖的時(shí)候敷鸦,self的計(jì)數(shù)沒有減少到0,而導(dǎo)致無法調(diào)用dealloc方法寝贡,出現(xiàn)了內(nèi)存泄露扒披。

所以最后我的解決辦法就是取消那些還沒有來得及執(zhí)行的延時(shí)函數(shù),代碼很簡單:

[NSObject cancelPreviousPerformRequestsWithTarget:self]

當(dāng)然你也可以一個(gè)一個(gè)得這樣用:

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]

加上了這個(gè)以后圃泡,dealloc方法就會(huì)被調(diào)用碟案,問題解決!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颇蜡,一起剝皮案震驚了整個(gè)濱河市价说,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌风秤,老刑警劉巖鳖目,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唁情,居然都是意外死亡疑苔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門甸鸟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惦费,“玉大人,你說我怎么就攤上這事抢韭⌒狡叮” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵刻恭,是天一觀的道長瞧省。 經(jīng)常有香客問我,道長鳍贾,這世上最難降的妖魔是什么鞍匾? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮骑科,結(jié)果婚禮上橡淑,老公的妹妹穿的比我還像新娘。我一直安慰自己咆爽,他們只是感情好梁棠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斗埂,像睡著了一般符糊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呛凶,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天男娄,我揣著相機(jī)與錄音,去河邊找鬼漾稀。 笑死模闲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的县好。 我是一名探鬼主播围橡,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缕贡!你這毒婦竟也來了翁授?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤晾咪,失蹤者是張志新(化名)和其女友劉穎收擦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谍倦,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡塞赂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了昼蛀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宴猾。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡圆存,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仇哆,到底是詐尸還是另有隱情沦辙,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布讹剔,位于F島的核電站油讯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏延欠。R本人自食惡果不足惜陌兑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望由捎。 院中可真熱鬧兔综,春花似錦、人聲如沸隅俘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽为居。三九已至碌宴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒙畴,已是汗流浹背贰镣。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留膳凝,地道東北人碑隆。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像蹬音,于是被迫代替她去往敵國和親上煤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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

  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的著淆,也是非常重要的劫狠, 在面試過程中是經(jīng)常會(huì)被問到的, ...
    made_China閱讀 1,202評(píng)論 0 7
  • 一永部、什么是runloop 字面意思是“消息循環(huán)独泞、運(yùn)行循環(huán)”。它不是線程苔埋,但它和線程息息相關(guān)懦砂。一般來講,一個(gè)線程一次...
    WeiHing閱讀 8,109評(píng)論 11 111
  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的,也是非常重要的荞膘, 在面試過程中是經(jīng)常會(huì)被問到的罚随, ...
    SOI閱讀 21,780評(píng)論 3 63
  • 對(duì)于從事 iOS 開發(fā)人員來說,所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,697評(píng)論 7 64
  • 一個(gè)人搖了一葉舟 櫓聲響 響脆了水色天光 水的深處 正煙靄微茫 山也蒼涼 水也蒼涼 人生多少悲與喜 都不在心上 別...
    島上君閱讀 216評(píng)論 1 5