面试题答案
一键面试@selector()底层原理
- 方法名转换为SEL类型:
- 在Objective - C中,
@selector()
是一个编译器指令。当编译器遇到@selector()
时,它会在编译期将传入的方法名(字符串形式)转换为SEL
类型。这个过程其实是通过查找预编译时生成的一个映射表来实现的。这个映射表将方法名(以字符串形式存储)与对应的SEL
值关联起来。 - 例如,对于一个类
MyClass
有方法-(void)myMethod;
,当使用@selector(myMethod)
时,编译器会在这个映射表中查找"myMethod"
对应的SEL
值并返回。
- 在Objective - C中,
- SEL在runtime中的数据结构和存储方式:
SEL
本质上是一个typedef struct objc_selector *SEL;
,它是一个指向objc_selector
结构体的指针。在runtime中,SEL
被用来唯一标识一个方法。- 存储方式上,runtime会为每个类维护一个方法列表,这个列表中每个方法都与一个
SEL
关联。并且,runtime有一个全局的缓存(高速缓存),用于快速查找SEL
对应的方法实现。当一个方法被调用时,runtime首先在缓存中查找对应的SEL
,如果找不到再去类的方法列表中查找。
自定义实现类似@selector()的设计思路和关键步骤
- 设计思路:
- 首先需要一个数据结构来存储方法名与自定义
SEL
的映射关系。可以使用哈希表(如NSMutableDictionary
,在实际C实现中可使用自定义哈希表),以方法名字符串为键,自定义的SEL
值为值。 - 对于每个类,需要维护一个独立的映射表,因为不同类可能有同名方法。
- 首先需要一个数据结构来存储方法名与自定义
- 关键步骤:
- 定义自定义SEL类型:
typedef int MySEL;
- 创建映射表:
NSMutableDictionary<NSString *, MySEL> *classSelectorMap = [NSMutableDictionary dictionary];
- 实现类似@selector()的函数:
MySEL mySelector(const char *methodName) { NSString *name = [NSString stringWithUTF8String:methodName]; MySEL sel = [classSelectorMap[name] intValue]; if (sel == 0) { static MySEL nextSel = 1; sel = nextSel++; classSelectorMap[name] = @(sel); } return sel; }
- 定义自定义SEL类型:
与原生@selector()的差异及应用场景
- 差异:
- 性能差异:原生
@selector()
是在编译期生成映射,效率极高。而自定义实现是在运行时构建映射,首次获取SEL
时会有一定性能开销。 - 唯一性:原生
SEL
在整个程序中是唯一的,不同类同名方法对应相同SEL
。自定义实现中,如果不同类使用相同方法名,可能会生成不同的SEL
值,除非进行特殊处理。 - 系统集成:原生
@selector()
与runtime紧密集成,可直接用于消息发送等runtime操作。自定义实现则需要额外适配才能用于runtime相关操作。
- 性能差异:原生
- 应用场景:
- 学习与研究:自定义实现有助于深入理解
@selector()
的原理,适合学习Objective - C runtime机制。 - 轻量级框架:在一些轻量级、对runtime依赖不深的类库中,自定义实现可以简化实现,避免引入过多runtime依赖。但对于大型、复杂的应用,原生
@selector()
因其高效性和与runtime的紧密集成更适合。
- 学习与研究:自定义实现有助于深入理解