导入跨类同名函数后,@selector如何确定目标调用函数?
@selector 对同名类方法的判定逻辑说明
首先明确核心结论:@selector 在编译阶段根本不会判定你要匹配哪个类的同名方法,它本身只是一个和具体类无关的、全局唯一的方法名映射标识。
1. @selector 的本质
- 它的运行时本质是生成一个
SEL(方法选择器)类型的常量,系统会把传入的方法名字符串,全局映射成唯一的内存值。 - 只要两个类实现的方法,方法名字符串完全一致(不管所属类是谁、实现逻辑是不是一样),生成的
SEL值就是全局相同的。
比如类A和类B都实现了- (void)test方法,那么不管在哪个类的代码里写@selector(test),得到的SEL值是完全一样的,和你导入了哪个类的头文件、当前代码写在哪个类的实现文件里没有任何关联。
2. 编译器对@selector的校验边界
编译器在处理@selector(方法名)时,仅会做非常有限的存在性校验,完全不会做你预期的“匹配到具体类方法”的判定:
- 校验逻辑只看:当前编译可见的头文件里,有没有任何一个类声明过这个同名方法——只要能找到至少一个,就不会抛编译错误,目的只是帮你避免手滑打错方法名导致运行时崩溃。
- 编译器完全不会做的事:
- 不会因为你在当前类里导入了另一个类的头文件,就把这个selector绑定到导入类的方法上
- 不会因为当前代码写在某一个类的实现文件里,就把这个selector绑定到当前类的方法上
- 从代码截图的表现就能验证:哪怕在类A中导入了类B,且两个类都实现了同名的
test方法,写@selector(test)也不会有任何编译歧义报错,因为编译器根本不关心这个方法归属于哪个类。
3. 真正决定调用哪个方法实现的逻辑
方法的具体实现查找,完全发生在运行时的消息转发流程里,和编译阶段写的@selector没有绑定关系:
- 当你拿着这个通用的
SEL给某个对象发消息触发调用时,运行时会以消息接收者为基准,在接收者对应的类的方法缓存、方法列表里,查找这个SEL对应的实现地址(IMP):- 如果你把这个SEL传给类A的实例,就会执行类A版本的
test方法 - 如果你把同一个SEL传给类B的实例,就会执行类B版本的
test方法
- 如果你把这个SEL传给类A的实例,就会执行类A版本的
- 这套机制也是UIControl事件回调、代理模式能通用传递SEL的核心支撑:SEL本身和具体类解耦,调用逻辑完全由传入的target决定。
- 常见的使用误区:很多开发者写
[target performSelector:@selector(test)]时,会误以为SEL绑定了自己最初参照的那个类的方法——实际上只要target的类实现了test方法,就一定会走target自己的实现,和你写selector时的参照对象没有任何关系。
4. 同名方法的避坑建议
- 如果两个类的同名方法需要被SEL区分识别,最根本的解决方案是给两个方法定义不同的方法名,不要依赖同名方法的复用。
- 如果必须要统一传递SEL做跨类的方法调用,不要只传SEL本身,需要额外绑定消息接收者的信息,或者使用
NSInvocation提前把接收者、方法、参数绑定成完整的调用单元,避免因为SEL的全局唯一性导致调用到非预期的实现。
内容的提问来源于stack exchange,提问作者Yuan Chen
相关产品推荐
相关产品推荐

