關於 performSelector 的一些小探討

本文首發在個人我的博客: blog.shenyuanluo.com,喜歡的朋友歡迎訂閱。html

考慮如下代碼,最終會輸出什麼?

  1. 例子①:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        NSLog(@"1 - %@", [NSThread currentThread]);
    
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                       withObject:nil];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,3,4
    • 緣由: 由於 performSelector:withObject: 會在當前線程當即執行指定的 selector 方法。
  2. 例子②:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSLog(@"1 - %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                       withObject:nil
                       afterDelay:0];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,4
    • 緣由: 由於 performSelector:withObject:afterDelay: 實際是往 RunLoop 裏面註冊一個定時器,而在子線程中,RunLoop 是沒有開啓(默認)的,全部不會輸出 3。官網 API 做以下解釋:
  3. 例子③:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSLog(@"1 - %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                       withObject:nil
                       afterDelay:0];
            [[NSRunLoop currentRunLoop] run];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    	
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,3,4
    • 緣由: 因爲 [[NSRunLoop currentRunLoop] run]; 會建立的當前子線程對應的 RunLoop 對象並啓動了,所以能夠執行 test 方法;而且 test 執行完後,RunLoop 中註冊的定時器已經無效,因此還能夠輸出 4 (對比 例子⑥例子)。
  4. 例子④:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSLog(@"1 - %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                         onThread:[NSThread currentThread]
                       withObject:nil
                    waitUntilDone:YES];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    	
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,3,4
    • 緣由: 由於 performSelector:onThread:withObject:waitUntilDone: 會在指定的線程執行,而執行的策略根據參數 wait 處理,這裏傳 YES 代表將會當即阻斷 指定的線程 並執行指定的 selector。官網 API 解釋以下:
  5. 例子⑤:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSLog(@"1 - %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                         onThread:[NSThread currentThread]
                       withObject:nil
                    waitUntilDone:NO];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    	
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,4
    • 緣由: 由於 performSelector:onThread:withObject:waitUntilDone: 會在指定的線程執行,而執行的策略根據參數 wait 處理,這裏傳 NO 代表不會當即阻斷 指定的線程 而是將 selector 添加到指定線程的 RunLoop 中等待時機執行。(該例子中,子線程 RunLoop 沒有啓動,全部沒有輸出 3)官網 API 解釋以下:
  6. 例子⑥:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSLog(@"1 - %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                         onThread:[NSThread currentThread]
                       withObject:nil
                    waitUntilDone:NO];
            [[NSRunLoop currentRunLoop] run];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    	
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,3
    • 緣由: 因爲 [[NSRunLoop currentRunLoop] run]; 已經建立的當前子線程對應的 RunLoop 對象並啓動了,所以能夠執行 test 方法;可是 test 方法執行完後,RunLoop 並無結束(使用這種啓動方式,RunLoop 會一直運行下去,在此期間會處理來自輸入源的數據,而且會在 NSDefaultRunLoopMode 模式下重複調用 runMode:beforeDate: 方法)因此沒法繼續輸出 4
  7. 例子⑦:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSLog(@"1 - %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                         onThread:[NSThread currentThread]
                       withObject:nil
                    waitUntilDone:NO];
            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    	
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,3
    • 緣由: 因爲 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; 已經建立的當前子線程對應的 RunLoop 對象並啓動了,所以能夠執行 test 方法;可是 test 方法執行完後,RunLoop 並無結束(使用這種啓動方式,能夠設置超時時間,在超時時間到達以前,runloop會一直運行,在此期間runloop會處理來自輸入源的數據,而且會在 NSDefaultRunLoopMode 模式下重複調用 runMode:beforeDate: 方法)因此沒法繼續輸出 4
  8. 例子⑧:
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        NSLog(@"1 - %@", [NSThread currentThread]);
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"2 - %@", [NSThread currentThread]);
            [self performSelector:@selector(test)
                         onThread:[NSThread currentThread]
                       withObject:nil
                    waitUntilDone:NO];
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                                     beforeDate:[NSDate distantFuture]];
            NSLog(@"4 - %@", [NSThread currentThread]);
        });
    }
    	
    - (void)test
    {
        NSLog(@"3 - %@", [NSThread currentThread]);
    }
    複製代碼
    • 輸出結果:1,2,3,4
    • 緣由: 因爲 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 已經建立的當前子線程對應的 RunLoop 對象並啓動了,所以能夠執行 test 方法;並且 test 方法執行完後,RunLoop 馬上結束(使用這種啓動方式 ,RunLoop 會運行一次,超時時間到達或者第一個 input source 被處理,則 RunLoop 就會退出)因此能夠繼續輸出 4

小結:

  1. 經常使用 performSelector 方法
    • 經常使用的 perform,是 NSObject.h 頭文件下的方法:
    - (id)performSelector:(SEL)aSelector;
    - (id)performSelector:(SEL)aSelector withObject:(id)object;
    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
    複製代碼
    • 能夠 delay 的 perform,是 NSRunLoop.h 頭文件下的方法:
    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
    複製代碼
    • 能夠 指定線程 的 perform,是 NSThread 頭文件下的方法:
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
    複製代碼
  2. RunLoop 退出方式:
    • 使用 - (void)run; 啓動,RunLoop 會一直運行下去,在此期間會處理來自輸入源的數據,而且會在 NSDefaultRunLoopMode 模式下重複調用 runMode:beforeDate: 方法;
    • 使用 - (void)runUntilDate:(NSDate *)limitDate; 啓動,能夠設置超時時間,在超時時間到達以前,RunLoop 會一直運行,在此期間 RunLoop 會處理來自輸入源的數據,而且也會在 NSDefaultRunLoopMode 模式下重複調用 runMode:beforeDate: 方法;
    • 使用 - (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; 啓動,RunLoop 會運行一次,超時時間到達或者第一個 input source 被處理,則 RunLoop 就會退出。
  3. 更多關於 NSRunLoop的退出方式 能夠看這篇博文

參考

  1. NSRunLoop的退出方式
相關文章
相關標籤/搜索