关于 performSelector 的一些小探讨

本文首发在我的个人博客: blog.shenyuanluo.com,喜欢的朋友欢迎订阅。

考虑以下代码,最终会输出什么?

  1. 例子①:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    复制代码- (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. 例子②:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    复制代码- (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. 例子③:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    复制代码- (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. 例子④:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    复制代码- (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. 例子⑤:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    复制代码- (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. 例子⑥:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    复制代码- (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. 例子⑦:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    复制代码- (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. 例子⑧:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    复制代码- (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 头文件下的方法:

      1
      2
      3
      复制代码- (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 头文件下的方法:

      1
      2
      复制代码- (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 头文件下的方法:

      1
      2
      3
      4
      5
      复制代码- (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的退出方式

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%