@property 本质上是一个“编译器特性”,但它必须依赖“运行时(Runtime)”的支持才能闭环。
如果用一句话概括: @property 是编译器帮你写的“代码模版”。
1. 为什么说是编译器特性?
在早期的 Objective-C 中,你需要手动声明成员变量(ivar),并手写 getter 和 setter 方法。这非常枯燥。
当你写下 @property (nonatomic, copy) NSString *name; 时,编译器(Clang)会自动为你完成以下三件事(即 Auto-synthesize):
- 生成成员变量: 自动在类中创建一个带下划线的变量
_name。 - 生成声明: 在
@interface中声明-(NSString *)name和-(void)setName:(NSString *)name。 - 生成实现: 在
@implementation中根据你的参数(copy,nonatomic)自动填充方法内部的逻辑。
证据: 如果你反编译代码,你会发现 @property 消失了,取而代之的是标准的 C 函数和 ivar 访问逻辑。这证明了它主要是在编译阶段进行的“语法糖”展开。
2. 为什么它离不开运行时(Runtime)?
虽然代码是编译器写的,但这些属性的**元数据(Metadata)**会被保存在二进制文件中,并在运行时由 Runtime 接管:
- 内省(Introspection): 你可以在运行时通过
class_copyPropertyList获取一个类所有的属性名和特性(Attributes)。 - KVC 访问: 当你调用
setValue:forKey:时,Runtime 会根据属性名动态查找对应的setter。 - 动态绑定: 属性的访问本质上还是发送消息。如果没有 Runtime 的消息分发,编译器生成的这些方法就无法被调用。
3. 核心参数的背后:编译器做了什么?
编译器会根据你括号里的参数,生成完全不同的代码逻辑:
| 参数 | 编译器生成的伪代码(Setter 内部) | 目的 |
|---|---|---|
assign | _value = newValue; | 简单的赋值,用于基本类型。 |
retain | [newValue retain]; [_value release]; _value = newValue; | 内存管理,增加引用计数。 |
copy | _value = [newValue copy]; | 防御性拷贝,防止外部修改原对象。 |
atomic | os_unfair_lock_lock(...); _value = newValue; ... | 自动加锁,保证赋值操作的原子性。 |
4. 特殊情况:如果你不想用编译器的自动化
有时候你并不想让编译器“代笔”,这时你会用到两个指令:
@synthesize:虽然现在默认开启,但你可以用它来给变量改名,比如@synthesize name = realName;。@dynamic:这是理解“运行时”的关键。 它告诉编译器:“不要帮我生成方法实现,也不要报错,我保证在运行时(或者通过手动手写)会有这个方法。”
总结
@property 是一个编译期指令,它极大地简化了符合 Cocoa 编程规范(即使用特定的方法名读写变量)的代码编写工作。但它的存在意义是为了支撑 Objective-C 那套高度动态的运行环境。