關於performSelector看我就夠了

一、performSelector簡單使用

performSelector(方法執行器),iOS中提供了以下幾種經常使用的調用方式bash

[self performSelector:@selector(sureTestMethod)];
[self performSelector:@selector(sureTestMethod)
           withObject:params];
[self performSelector:@selector(sureTestMethod)
           withObject:params
           withObject:params2];
......
複製代碼

performSelector響應Objective-C動態性,將方法的綁定延遲到運行時,所以編譯階段不會檢測方法有效性,即方法不存在也不會提示報錯。反之由於此特性,performSelector也普遍用於動態化和組件化的模塊中。多線程

若是方法名稱也是動態不肯定的,會提示以下警告:app

SEL selector = @selector(dynamicMethod);
[self performSelector:selector];
複製代碼
⚠️ PerformSelector may cause a leak because its selector is unknown
複製代碼

意爲由於當前方法名未知可能會引發內存泄露相關問題。 能夠經過以下代碼忽略此警告async

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector:selector];
#pragma clang diagnostic pop
複製代碼

performSelector默認最多隻可傳遞兩個參數,若需多參可將參數封裝爲NSArray、NSDictionary、NSInvocation進行傳遞。另外方法調用本質都是消息機制,也能夠經過msg_send實現。oop

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");
}
複製代碼

二、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默認不開啓,所以沒法響應方法。ui

因此咱們嘗試在GCD Block中添加 [[NSRunLoop currentRunLoop]run];spa

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任一事件才能觸發開啓。code

簡言之以下代碼在執行 [[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

三、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
複製代碼

四、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的內容暫時寫到這裏了,有其餘補充歡迎評論~

iOS執行器performSelector詳解

相關文章
相關標籤/搜索