在 Objective-C 中,@interface 和 @implementation 的设计完美体现了“C 语言的静态声明”与“Smalltalk 风格的动态响应”的结合。
我们可以从**编译期(Compile Time)和运行时(Runtime)**两个阶段来拆解它们扮演的角色:
1. @interface (接口声明)
这一部分主要面向编译器,它类似于 C 语言的 .h 头文件,起到了“契约”的作用。
在编译期(Compiler):
- 布局确定(Layout): 编译器通过
@interface中声明的实例变量(ivar)来计算对象的内存布局(Size)。 - 类型检查: 当你在代码里写
[obj method]时,编译器会去查看obj所属类的@interface。如果里面没声明这个方法,编译器会报警告或错误(除非你用id类型)。 - 符号映射: 它定义了类的名字、父类、实现的协议以及属性(Property)的访问权限。
在运行时(Runtime):
- 角色弱化: 一旦程序编译完成,
@interface作为一个文本定义就不再直接存在了。它的信息被固化到了二进制文件的 Read-Only Data 区。 - 镜像加载: 当 App 启动加载镜像(Image)时,Runtime 会读取这些固化后的类信息,在内存中注册对应的类结构体(
objc_class)。
2. @implementation (具体实现)
这一部分既是代码的所在地,也是**消息机制(Messaging)**的弹药库。
在编译期(Compiler):
- 函数转换: 编译器会将你写的每个方法转换为普通的 C 函数。例如
-(void)doSomething会变成void _i_ClassName_doSomething(id self, SEL _cmd)。 - 属性合成: 编译器会自动生成
getter和setter的代码,以及对应的底层变量(如果你没有手动@synthesize)。
在运行时(Runtime):核心舞台
这是 Smalltalk 风格消息机制发挥作用的地方:
- 方法列表(Method List)注册: 当类被加载到内存时,
@implementation中写的方法会被登记在该类的 Method List 中。每个条目包含:SEL(方法名/选择子)和IMP(转换后的 C 函数指针)。 - 动态绑定: 当发送消息
[obj doSomething]时,Runtime 完全不管编译期是怎么声明的,它直接去类的方法列表里通过SEL找IMP。 - 动态修改: 既然
@implementation的结果是运行时的一个列表,你就可以在程序跑起来后,通过class_addMethod或method_exchangeImplementations(Method Swizzling) 动态地替换或增加实现。
总结对比
| 阶段 | @interface 的角色 | @implementation 的角色 |
|---|---|---|
| 编译期 | 合同工:告诉编译器对象长什么样,有哪些方法可以叫,不符合就报错。 | 加工厂:把逻辑代码编译成 C 函数,准备好供 Runtime 调用的数据结构。 |
| 运行时 | 静态参考:提供类名、父类等基础元数据。 | 动态执行:提供方法列表映射。消息传递的核心就是根据 SEL 在这个实现列表中找代码地址。 |
深度理解:为什么说它是 Smalltalk 风格?
在 C++ 中,如果你在类里增加一个方法,必须重新编译所有引用该类的代码,因为函数偏移量在编译期就定死了。
而在 Objective-C 中,@interface 只是个“暗示”,真正的权力在 @implementation 加载到 Runtime 后的那个方法映射表里。你可以只给一个对象发消息,而不关心它是否在 @interface 里声明过(通过 performSelector:),只要 @implementation 里有,或者运行时动态塞进去了,程序就能跑通。这就是 C 的形式 + Smalltalk 的内核。