performSelector 详解 —— 从 What 到 When

缘起

我最近被一位面试官问:

  • performSelector是咱们常用的办法,能不能从Runloop的视点说说?
  • performSelector跟咱们直接调用一个办法有什么区别?

一会儿就把我搞蒙了,连performSelector都没用过,最多在项目代码里读到,只知道大概是干嘛的,现在马上来搞懂它!

performSelector 是咱们常用的办法,能不能从 Runloop 的视点说说?

这个办法的入参是一个 selector,selector 属于 SEL 类型,用来代表一个函数,实际上是一个 C 的 string。打印一个 selector 会得到对应办法的姓名。详见官方文档

这儿他要问的其实是performSelector:withObject:afterDelay办法(Apple 文档在这儿),只要这个才跟 Runloop 有关系。这个办法会生成一个定时器 Timer,并把它添加到当前线程的 Runloop 上,到了预订时间后 Runloop 好去履行对应的 selector (即履行办法)。

+ (void)performSelectorInMainThread {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self performSelector:@selector(test) withObject:nil afterDelay:1.0];
    });
}
+ (void)test {
    NSLog(@"This is test method");
}

默许是 NSDefaultRunLoopMode,当传入的时间距离到的时分,就查看当前 Runloop 是否是那个 mode,假如是就会成功履行。假如不是,那么就要等一等,等 Runloop 的 mode 匹配。

留意:假如是在子线程中调用 performSelector:withObject:afterDelay 办法,那就得手动获取一下 runloop,一起得让子线程中的 Runloop 处于 run 状态。由于子线程中的 Runloop 不是默许创建,更不会默许 run 起来。

+ (void)performSelectorInOtherThread {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"1");
        NSRunLoop *runloop = [NSRunLoop currentRunLoop]; // 注释这句,则不会履行 test 办法
        [self performSelector:@selector(test) withObject:nil afterDelay:1.0];
        [runloop run];// 注释这句,也不会履行 test 办法; 
        //一起,这句需求放在 performSelector 后面,对应的 timer 才会被添加到 Runloop 中
        NSLog(@"3");
    });
}
+ (void)test {
    NSLog(@"This is test method");
}

这儿其实也能够引申两个问题:

  1. 如何查看 Runloop 拥有的「源」?
  2. Runloop 在什么时分会退出?(在本示例代码中,“3” 是在 Runloop 退出的时分打印的,那 Runloop 怎么为啥会退出?)
    想了解概况,请看Runloop 与 performSelector

performSelector 跟咱们直接调用一个办法有什么区别?

performSelector是运行时决议的,意味着用performSelector能够到运行时才决定要履行哪个办法,因此编译器不会查看performSelector的入参所代表的办法是否已经被caller目标完成。(因此,这时你能够在「不 import 头文件」的情况下调用办法)

而对于一般的办法调用,编译器会严厉把控当前 caller 是否完成了该办法,假如没有,底子过不了编译。

这就有点类似于 KVC(Key-Value-Coding),用一个字符串去调用一个函数。

performSelector 的运用

由于对应的 selector 有或许完成,也或许没完成,所以它常常跟respondsToSelector:搭配运用,如下代码:

// 查看 self 目标是否完成了 test 办法
if([self respondsToSelector:@selector(test)]) {
    [self performSelector:@selector(test)];
}

另外,根据 Apple 的文档描绘,performSelector 或许会带来内存办理问题:

But use caution when doing this. Different messages require different memory management strategies for their returned objects, and it might not be obvious which to use.
Usually the caller isn’t responsible for the memory of a returned object, but that’s not true when the selector is one of the creation methods, such ascopy. SeeMemory Management PolicyinAdvanced Memory Management Programming Guidefor a description of ownership expectations. Depending on the structure of your code, it might not be clear which kind of selector you are using for any given invocation.
Due to this uncertainty, the compiler generates a warning if you supply a variable selector while using ARC to manage memory. Because it can’t determine ownership of the returned object at compile-time, ARC makes the assumption that the caller doesnotneed to take ownership, but this may not be true. The compiler warning alerts you to the potential for a memory leak.

粗心是,假如提供的 selector 是结构办法,那么或许会带来所有权的不确认,进一步导致内存泄漏 的问题。假如确认 selector 不会返回值,主张改用performSelectorOnMainThread:withObject:waitUntilDone:

也能够改用 NSInvokation,构建一个能够返回值的 message(selector 能够被看做是一个 message,由于 Objective-C 调用办法的方法是「音讯转发」)

performSelector 的运用场景

动态化和组件化

多线程

// 主线程
- (void)performSelectorOnMainThread:(SEL)aSelector 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 modes:(nullable NSArray<NSString *> *)array

延后履行

[self performSelector:@selector(test) withObject:nil afterDelay:1.0];

参考资料