在 Objective-C Runtime 中,Protocol(协议)被视为一种特殊的对象。它不像 Class 那样拥有自己的实例变量空间,而是一套关于方法的“蓝图”或“描述符” 。
在底层,协议由结构体 protocol_t 表示。
1. 核心数据结构:protocol_t
每个协议在编译后都会生成一个 protocol_t 结构体。它的设计目标是完整描述协议的继承关系以及它所包含的四类方法。
C++
struct protocol_t : objc_object {
const char *mangledName; // 协议名称
struct protocol_list_t *protocols; // 继承的其他协议列表
method_list_t *instanceMethods; // @required 实例方法
method_list_t *classMethods; // @required 类方法
method_list_t *optionalInstanceMethods; // @optional 实例方法
method_list_t *optionalClassMethods; // @optional 类方法
struct property_list_t *instanceProperties; // 协议定义的属性
// ... 还有 size 和一些元数据
};
关键点:
- 四种列表分立:Runtime 将
required和optional方法完全分开存储。这也是为什么respondsToSelector:在运行时可以根据方法存在的列表来推断协议实现情况。 - 协议继承:
protocols指针允许协议像类一样形成继承链(例如UITableViewDelegate继承自UIScrollViewDelegate)。
2. 协议的注册与存储
协议在 Runtime 中是单例存在的。
- 全局 Hash 表:所有的协议在加载镜像(Images)时,都会被注册到一个全局的哈希表
protocols中。 - 获取方式:当你使用
@protocol(MyProtocol)时,编译器会将其转化为对全局表中该协议对象的引用。如果该协议尚未加载,Runtime 会负责查找并返回。
3. 类与协议的关联
当一个类(Class)声明遵循某个协议时,这个信息会被记录在类的 class_readonly_t(只读数据)中。
protocol_list_t:类的结构体中有一个指向协议列表的指针。- 校验逻辑:当你调用
conformsToProtocol:时,Runtime 会递归遍历该类及其父类的协议列表,以及这些协议继承的子协议。
4. 运行时动态创建协议
由于协议在底层是结构体,Runtime 允许我们在程序运行期间动态创建一个协议:
objc_allocateProtocol:分配内存并命名。protocol_addMethodDescription:向required或optional列表中添加方法描述。objc_registerProtocol:一旦注册,协议就变为只读,可以被类引用。
5. 总结:Protocol 的本质
| 维度 | Protocol 在 Runtime 中的表现 |
|---|---|
| 本质 | 描述方法的结构体(蓝图),不承载具体实现。 |
| 存储 | 存在于全局哈希表中,通过名称唯一标识。 |
| 方法查找 | objc_msgSend 不通过协议查找方法,而是直接找类的方法列表。协议仅用于类型检查和文档约束。 |
| 与类的关系 | 类持有指向协议对象的指针列表。 |
💡 深度启发
协议本身不存储 IMP(函数指针) 。它只存储方法名(SEL)和类型编码(Type Encoding)。这就是为什么协议可以定义规范,但具体的执行逻辑必须由遵循该协议的类通过其 method_list 来提供。