本文首發在我的個人部落格: blog.shenyuanluo.com,喜歡的朋友歡迎訂閱。
考慮以下程式碼,最終會輸出什麼?
- 例子①:
- (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 方法。
- 輸出結果:
- 例子②:
- (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 作如下解釋:
- 輸出結果:
- 例子③:
- (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
(對比 例子⑥例子)。
- 輸出結果:
- 例子④:
- (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 解釋如下:
- 輸出結果:
- 例子⑤:
- (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 解釋如下:
- 輸出結果:
- 例子⑥:
- (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
。
- 輸出結果:
- 例子⑦:
- (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
。
- 輸出結果:
- 例子⑧:
- (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
。
- 輸出結果:
小結:
- 常用 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; 複製程式碼
- 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 就會退出。
- 使用
- 更多關於 NSRunLoop的退出方式 可以看這篇博文