performSelector 详解 —— 从 What 到 When

缘起

我最近被一位面试官问:

  • performSelector 是我们常用的方法,能不能从 Runloop 的角度说说?
  • performSelector 跟我们直接调用一个方法有什么区别?

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

performSelector 是我们常用的方法,能不能从 Runloop 的角度说说?

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

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

objc 复制代码
+ (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 起来。

objc 复制代码
+ (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: 搭配使用,如下代码:

objc 复制代码
// 检查 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 as copy. See Memory Management Policy in Advanced Memory Management Programming Guide for 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 does not need 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 的使用场景

动态化和组件化

多线程

objc 复制代码
// 主线程
- (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

延后执行

objectivec 复制代码
[self performSelector:@selector(test) withObject:nil afterDelay:1.0];

参考资料

相关推荐
TechPioneer_lp19 分钟前
腾讯客户端开发岗位 LeetCode 高频题汇总(2026版)
算法·leetcode·面试·求职招聘·笔试·腾讯校招·leetcode高频题
程序员爱钓鱼36 分钟前
Go并发同步核心库:syn 包深度指南
后端·面试·go
赵长辉37 分钟前
牛客面试Top101: BM8 表达式求值【java,go】
算法·面试
Fairy要carry44 分钟前
面试-单 Agent 上下文膨胀问题
chrome·面试·职场和发展
小江的记录本1 小时前
【HashMap】HashMap 系统性知识体系全解(附《HashMap 面试八股文精简版》)
java·前端·后端·容器·面试·hash·哈希
星辰_mya1 小时前
三级缓存破局:Spring 如何优雅解决循环依赖?
java·spring·缓存·面试
洛邙1 小时前
互联网大厂Java求职面试实录:Spring Boot与微服务实战解析
java·spring boot·缓存·微服务·面试·分布式事务·电商
代码探秘者1 小时前
【大模型应用】5.深入理解向量数据库
java·数据库·后端·python·spring·面试
weixin_404157681 小时前
Java高级面试与工程实践问题集(二)
java·开发语言·面试
96773 小时前
力扣面试经典150 88. 合并两个有序数组 归并排序的merge函数
算法·leetcode·面试