本文首发在我的个人博客: 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的退出方式 可以看这篇博文