1、performSelector簡單使用
performSelector(方法執行器),iOS中提供瞭如下幾種常用的呼叫方式
[self performSelector:@selector(sureTestMethod)];
[self performSelector:@selector(sureTestMethod)
withObject:params];
[self performSelector:@selector(sureTestMethod)
withObject:params
withObject:params2];
......
複製程式碼
performSelector響應Objective-C動態性,將方法的繫結延遲到執行時,因此編譯階段不會檢測方法有效性,即方法不存在也不會提示報錯。反之因為此特性,performSelector也廣泛用於動態化和元件化的模組中。
如果方法名稱也是動態不確定的,會提示如下警告:
SEL selector = @selector(dynamicMethod);
[self performSelector:selector];
複製程式碼
⚠️ PerformSelector may cause a leak because its selector is unknown
複製程式碼
意為因為當前方法名未知可能會引起記憶體洩露相關問題。 可以通過如下程式碼忽略此警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector];
#pragma clang diagnostic pop
複製程式碼
performSelector預設最多隻可傳遞兩個引數,若需多參可將引數封裝為NSArray、NSDictionary、NSInvocation進行傳遞。另外方法呼叫本質都是訊息機制,也可以通過msg_send實現。
id params;
id params2;
id params3;
SEL selector = NSSelectorFromString(@"sureTestMethod:params2:params3:");
objc_msgSend(self, selector,params,params2,params3);
- (void)sureTestMethod:(id)params params2:(id)params2 params3:(id)params3 {
NSLog(@"sureTestMethod-multi-parameter");
}
複製程式碼
2、performSelector延遲呼叫
[self performSelector:@selector(sureTestMethod:)
withObject:params
afterDelay:3];
複製程式碼
此方法意為在當前Runloop中延遲3秒後執行selector中方法。 使用該方法需要注意以下事項: 在子執行緒中呼叫performSelector: withObject: afterDelay:預設無效,如下程式碼並不會列印sureTestMethodCall
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(sureTestMethod:)
withObject:params
afterDelay:3];
});
- (void)sureTestMethod:(id)objcet {
NSLog(@"sureTestMethodCall");
}
複製程式碼
這是因為performSelector: withObject: afterDelay:是在當前Runloop中延時執行的,而子執行緒的Runloop預設不開啟,因此無法響應方法。
所以我們嘗試在GCD Block中新增 [[NSRunLoop currentRunLoop]run];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(sureTestMethod:)
withObject:params
afterDelay:3];
[[NSRunLoop currentRunLoop]run];
});
複製程式碼
執行程式碼發現可以正常列印sureTestMethodCall。
這裡有個坑需要注意,曾經嘗試將 [[NSRunLoop currentRunLoop]run]新增在performSelector: withObject: afterDelay:方法前,但發現延遲方法仍然不呼叫,這是因為若想開啟某執行緒的Runloop,必須具有timer、source、observer任一事件才能觸發開啟。
簡言之如下程式碼在執行 [[NSRunLoop currentRunLoop]run]前沒有任何事件新增到當前Runloop,因此該執行緒的Runloop是不會開啟的,從而延遲事件不執行。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[[NSRunLoop currentRunLoop]run];
[self performSelector:@selector(sureTestMethod:)
withObject:params
afterDelay:3];
});
複製程式碼
關於Runloop,可詳見:深入理解RunLoop
3、performSelector取消延遲
我們在View上放置一個Button,預期需求是防止暴力點選,只響應最後一次點選時的事件。
此需求我們可以通過cancelPreviousPerformRequestsWithTarget來進行實現。cancelPreviousPerformRequestsWithTarget的作用為取消當前延時任務。在執行延遲事件前取消當前存在的延遲任務即可實現如上效果。
- (IBAction)buttonClick:(id)sender {
id params;
[[self class]cancelPreviousPerformRequestsWithTarget:self
selector:@selector(sureTestMethod:)
object:params];
[self performSelector:@selector(sureTestMethod:)
withObject:params
afterDelay:3];
}
- (void)sureTestMethod:(id)objcet {
NSLog(@"sureTestMethodCall");
}
複製程式碼
重複點選後,列印結果如下,只響應了一次點選
2019-05-06 11:29:50.352157+0800 performSelector[14342:457353] sureTestMethodCall
複製程式碼
4、performSelector模擬多執行緒
我們可以通過performSelectorInBackground將某selector任務放在子執行緒中
[self performSelectorInBackground:@selector(sureTestMethod:)
withObject:params];
- (void)sureTestMethod:(id)objcet {
NSLog(@"%@",[NSThread currentThread]);
}
複製程式碼
//<NSThread: 0x600003854080>{number = 3, name = (null)}
複製程式碼
列印結果可見當前方法執行在子執行緒中。
回到主執行緒執行我們可以通過方法
[self performSelectorOnMainThread:@selector(sureTestMethod)
withObject:params
waitUntilDone:NO];
複製程式碼
waitUntilDone表示是否等待當前selector任務完成後再執行後續任務。示例如下,waitUntilDone為YES時,列印1,2,3。為NO時列印1,3,2。
NSLog(@"1");
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:NO];
NSLog(@"3");
複製程式碼
- (void)test {
sleep(3);
NSLog(@"2");
}
複製程式碼
另外performSelector還提供了將任務執行在某個指定執行緒的操作
[self performSelector:@selector(sureTestMethod:)
onThread:thread
withObject:params
waitUntilDone:NO];
複製程式碼
使用該方法一定要注意所線上程生命週期是否正常,若thread已銷燬不存在,而performSelector強行執行任務在該執行緒,會導致崩潰:
NSThread *thread = [[NSThread alloc]initWithBlock:^{
NSLog(@"do thread event");
}];
[thread start];
[self performSelector:@selector(sureTestMethod:)
onThread:thread
withObject:params
waitUntilDone:YES];
複製程式碼
上述程式碼會導致崩潰,崩潰資訊為:
*** Terminating app due to uncaught exception 'NSDestinationInvalidException',
reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]:
target thread exited while waiting for the perform'
複製程式碼
因為thread開啟執行do thread event完畢後即退出銷燬,所以在等待執行任務時Thread已不存在導致崩潰。
好了,關於performSelector的內容暫時寫到這裡了,有其他補充歡迎評論~