performSelector

2021-04-09  本文已影响0人  QYCD

performSelector较常见的三个方法:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

对这三个方法进行测试:

    NSLog(@"当前线程: %@", [NSThread currentThread]);
    [self performSelector:@selector(testMethod1)];
    [self performSelector:@selector(testMethod2:) withObject:@"China"];
    [self performSelector:@selector(testMethod3:arg2:) withObject:@"China" withObject:@"Beijing"];
    
    dispatch_queue_t queue = dispatch_queue_create("qqq", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"当前线程: %@", [NSThread currentThread]);
        [self performSelector:@selector(testMethod1)];
        [self performSelector:@selector(testMethod2:) withObject:@"China"];
        [self performSelector:@selector(testMethod3:arg2:) withObject:@"China" withObject:@"Beijing"];
    });

- (void)testMethod1 {
    NSLog(@"test method thread: %@", [NSThread currentThread]);
}

- (void)testMethod2:(NSString *)arg1 {
    NSLog(@"test method arg1: %@ thread: %@", arg1, [NSThread currentThread]);
}

- (void)testMethod3:(NSString *)arg1 arg2:(NSString *)arg2 {
    NSLog(@"test method arg1: %@ arg2: %@ thread: %@", arg1, arg2, [NSThread currentThread]);
}

打印结果:

[ViewController.m:35行] 当前线程: <NSThread: 0x282172ec0>{number = 1, name = main}
[ViewController.m:54行] test method thread: <NSThread: 0x282172ec0>{number = 1, name = main}
[ViewController.m:58行] test method arg1: China thread: <NSThread: 0x282172ec0>{number = 1, name = main}
[ViewController.m:62行] test method arg1: China arg2: Beijing thread: <NSThread: 0x282172ec0>{number = 1, name = main}
[ViewController.m:41行] 当前线程: <NSThread: 0x28212a800>{number = 5, name = (null)}
[ViewController.m:54行] test method thread: <NSThread: 0x28212a800>{number = 5, name = (null)}
[ViewController.m:58行] test method arg1: China thread: <NSThread: 0x28212a800>{number = 5, name = (null)}
[ViewController.m:62行] test method arg1: China arg2: Beijing thread: <NSThread: 0x28212a800>{number = 5, name = (null)}

可以看到上述三个方法调用都是直接执行,相当于直接通过对象调用方法,即[self performSelector:@selector(method)] 与 [self method]执行结果是一样的。方法内运行的线程就是调用performSelector所在的线程。

若调用未声明的方法,如

[self performSelector:@selector(noExistMethod)];

编译时并不会报错,可以看到编译器仅会报警告:


image.png

但当运行时,会崩溃:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController noExistMethod]: unrecognized selector sent to instance 0x1015034a0'
performSelector是运行时系统负责去找方法,在编译时不会对调用的方法做检查,只有在运行时才会检查,如果方法存在则调用,不存在则报错崩溃。我们可以通过- (BOOL)respondsToSelector:(SEL)aSelector;方法判断对象是否实现了要调用的方法。

查看上述上个方法的源码(objc4-818.2版本):

- (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}

- (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2);
}

doesNotRecognizeSelector:
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}
performSelector的延迟执行方法
- (void)performSelector:(SEL)aSelector 
             withObject:(id)anArgument 
             afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector 
             withObject:(id)anArgument 
             afterDelay:(NSTimeInterval)delay 
                inModes:(NSArray<NSRunLoopMode> *)modes;

查看文档:

This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
If you want the message to be dequeued when the run loop is in a mode other than the default mode, use the performSelector:withObject:afterDelay:inModes: method instead. If you are not sure whether the current thread is the main thread, you can use the performSelectorOnMainThread:withObject:waitUntilDone: or performSelectorOnMainThread:withObject:waitUntilDone:modes: method to guarantee that your selector executes on the main thread. To cancel a queued message, use the cancelPreviousPerformRequestsWithTarget: or cancelPreviousPerformRequestsWithTarget:selector:object: method.

Special Considerations
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly. One common context where you might call this method and end up registering with a runloop that is not automatically run on a regular basis is when being invoked by a dispatch queue. If you need this type of functionality when running on a dispatch queue, you should use dispatch_after and related methods to get the behavior you want.

这个方法设置一个timer,以便在当前线程的runloop上执行aSelector消息。这个timer的默认模式是NSDefaultRunLoopMode。当timer触发时,会尝试从runloop中取出消息,如果这个runloop在运行并且模式是NSDefaultRunLoopMode就会执行它,否则,会等待知道runloop处于NSDefaultRunLoopMode模式。

如果希望在runloop处于NSDefaultRunLoopMode以外的其他模式时使消息出队,改用performSelector:withObject:afterDelay:inModes:。如果不确定当前线程是否为主线程,可以使用performSelectorOnMainThread:withObject:waitUntilDone:或者performSelectorOnMainThread:withObject:waitUntilDone:modes:来确保选择器在主线程上执行。要取消排队的消息,使用cancelPreviousPerformRequestsWithTarget:或cancelPreviousPerformRequestsWithTarget:selector:object:方法。

特别注意
此方法向其当前上下文的runloop进行注册,并依赖于runloop才能正确执行。一种常见情况是,当在 dispatch queue上调用这个方法,但是runloop并没有启动,这个方法是不会运行的。如果想使用这个延迟功能在dispatch queue上,则应使用dispatch_after和相关方法来获得所需的行为。

在主线程调用
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    //主线程中,其runloop是默认开启的,所以方法可以直接执行
    [self performSelector:@selector(testMethod2:) withObject:@"China" afterDelay:1];
}

- (void)testMethod2:(NSString *)arg1 {
    NSLog(@"test method arg1: %@ thread: %@", arg1, [NSThread currentThread]);
}

运行结果:

[ViewController.m:45行] test method arg1: China thread: <NSThread: 0x2838a35c0>{number = 1, name = main}
在子线程调用
dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 不会执行, 此时这个线程的runloop默认没有开启
        [self performSelector:@selector(testMethod2:) withObject:@"China" afterDelay:1];
    });

运行,发现testMethod2:方法并没有被执行,因为主线程的runloop系统默认已经启动,而子线程的runloop需要我们手动启动。

dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 不会执行, 调用run方法只是尝试开启当前线程中的runloop, 但是如果该线程中并没有任何事件(source、time、observer),runloop会直接退出,并不会启动
        [[NSRunLoop currentRunLoop] run];
        [self performSelector:@selector(testMethod2:) withObject:@"China" afterDelay:1];
    });
dispatch_queue_t queue = dispatch_queue_create("并发队列", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 成功调用方法
        [self performSelector:@selector(testMethod2:) withObject:@"China" afterDelay:1];
        [[NSRunLoop currentRunLoop] run];
    });

参考:
iOS performSelector方法总结

上一篇下一篇

猜你喜欢

热点阅读