消息處理之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)用碟案,問題解決!