对于新手来说,@property
、@synthesize
和实例变量(instance variable)之间的关系常常让人困惑。
我们来一步步拆解它。
一、背景:在有 @synthesize
之前,我们是怎么做的?
在 Objective-C 中,对象的"数据"通常存储在**实例变量(Instance Variables,简称 ivars)**中。这些变量属于类的每一个实例。为了遵循面向对象的封装原则,我们不应该让外部直接访问这些变量。
取而代之,我们提供访问器方法(Accessor Methods):
- Getter 方法:用于读取实例变量的值。
- Setter 方法:用于设置实例变量的值。
手动编写的时代(非常繁琐):
假设我们有一个 Person
类,需要一个 name
属性。在早期,你需要这样做:
-
在
.h
文件中声明实例变量和方法:objectivec// Person.h @interface Person : NSObject { // 1. 声明实例变量 (ivar) NSString *_name; } // 2. 声明 getter 和 setter 方法 - (NSString *)name; - (void)setName:(NSString *)newName; @end
(注意:实例变量通常用下划线
_
开头,这是一个长期以来的编码约定。) -
在
.m
文件中手动实现方法:objectivec// Person.m @implementation Person // 3. 手动实现 getter - (NSString *)name { return _name; } // 4. 手动实现 setter - (void)setName:(NSString *)newName { if (_name != newName) { _name = [newName copy]; // 为了内存管理,使用copy } } @end
你可以看到,为每一个属性都这样写一遍是非常重复和乏味的工作。
二、@property
和 @synthesize
的诞生:让编译器为我们工作
为了解决上述的繁琐问题,Objective-C 引入了 @property
和 @synthesize
。
1. @property
(属性)
@property
是一个声明,它告诉编译器:"我希望这个类有一个名为 name
的属性。请你帮我自动声明它的 getter 和 setter 方法。"
使用 @property
后,.h
文件变得非常简洁:
objectivec
// Person.h
@interface Person : NSObject
// 这一行代码,就等于上面手动声明的 getter 和 setter 方法
@property (nonatomic, copy) NSString *name;
@end
@property
只做了声明,并没有做实现! 这时,编译器知道了应该存在 -name
和 -setName:
这两个方法,但方法的具体实现(方法体里的代码)还没有。
2. @synthesize
(合成)
@synthesize
正是用来完成实现 这一步的。它告诉编译器:"请你帮我自动生成 在 @property
中声明的 getter 和 setter 方法的实现代码。"
objectivec
// Person.m
@implementation Person
// 告诉编译器:请为 'name' 属性生成 getter 和 setter 方法
@synthesize name;
@end
@synthesize name;
这一行代码,在编译时会自动展开成类似我们前面手动编写的 getter 和 setter 方法。
默认情况下,@synthesize name;
还会做一件事:它会自动生成一个名为 name
(与属性同名)的实例变量来存储数据。
三、@synthesize
的具体用法和自定义
@synthesize
最常见的用法是将属性与一个特定名称的实例变量关联起来。
语法是:@synthesize propertyName = instanceVariableName;
propertyName
:你在@property
中声明的属性名。instanceVariableName
:你希望用来存储这个属性值的实例变量名。
示例1:默认行为
objectivec
@synthesize name;
// 等价于
@synthesize name = name; // 告诉编译器,用一个名为 'name' 的实例变量
示例2:最推荐的做法(使用下划线 _
)
objectivec
@synthesize name = _name;
这行代码告诉编译器:
- 为
name
属性生成 getter (-name
) 和 setter (-setName:
)。 - 这些方法将操作一个名为
_name
的实例变量。 - 如果
_name
这个实例变量不存在,请自动帮我创建一个。
这很好地遵循了"实例变量用下划线开头"的编码约定,可以让你在类的内部实现中,清晰地区分是在直接访问实例变量(_name
)还是通过 getter/setter 调用属性(self.name
)。
四、现代 Objective-C 的进化:自动合成 (Auto-synthesis)
你可能会发现,在现在用 Xcode 创建的新项目中,你几乎看不到 @synthesize
的身影。为什么呢?
因为从 LLVM 4.0 编译器开始,@synthesize
已经是自动的了!
现在的规则是:
只要你在
.h
或.m
文件中声明了@property
,并且你没有同时手动实现该属性的 getter 和 setter 方法 ,编译器就会在后台为你自动执行以下操作:
@synthesize propertyName = _propertyName;
这就是所谓的自动合成 (Auto-synthesis)。
所以,今天我们这样写代码就足够了:
objectivec
// Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
// Person.m
@implementation Person
// 这里什么都不用写!编译器会自动帮你合成 getter/setter 和 _name 实例变量
@end
五、那么,现在什么时候还必须手动写 @synthesize
?
尽管有了自动合成,但在以下几种特定场景中,你仍然需要手动添加 @synthesize
:
场景1:当你同时重写了 getter 和 setter
如果你自己实现了所有的访问器方法,编译器会认为:"开发者接管了一切,他可能不希望我再创建实例变量了。" 于是它就不会自动合成实例变量。
objectivec
// Person.m
@implementation Person
// 我想在赋值时打印日志
- (void)setName:(NSString *)name {
_name = [name copy]; // 错误! _name 在这里未定义!
NSLog(@"设置了新的名字: %@", _name);
}
// 我想在取值时也打印日志
- (NSString *)name {
NSLog(@"获取了名字");
return _name; // 错误! _name 在这里未定义!
}
@end
为了解决这个问题,你必须明确告诉编译器:"即使我实现了所有方法,我依然需要你帮我创建那个实例变量。"
正确的做法:
objectivec
@implementation Person
@synthesize name = _name; // 手动添加这一行!
- (void)setName:(NSString *)name {
_name = [name copy]; // 现在 _name 就存在了
NSLog(@"设置了新的名字: %@", _name);
}
- (NSString *)name {
NSLog(@"获取了名字");
return _name; // 现在 _name 就存在了
}
@end
场景2:当你在协议中声明属性时
这正好回到了你最初的问题。当一个类遵守的协议中定义了属性,你需要在类中满足这个协议的要求。
objectivec
@protocol Nameable
@property (nonatomic, copy) NSString *name;
@end
// MyClass 遵守 Nameable 协议
@interface MyClass : NSObject <Nameable>
@end
@implementation MyClass
// 为了满足协议,MyClass 必须提供 name 属性的 getter 和 setter
// 以前的做法是手动写 @synthesize
@synthesize name = _name;
@end
但是,得益于自动合成,现代做法更简单。你只需要在你的类里重新声明一下这个属性,自动合成就会生效:
objectivec
// MyClass.h
@interface MyClass : NSObject <Nameable>
// 虽然协议里已经有了,但在类里再声明一次,以触发自动合成
@property (nonatomic, copy) NSString *name;
@end
// MyClass.m
@implementation MyClass
// 什么都不用写,自动合成会搞定一切
@end
所以,即便是在协议的场景下,除非你遇到场景1(同时重写 getter/setter),否则通常也不需要手动写 @synthesize
了。
总结
术语 | 角色 | 现代状态 |
---|---|---|
实例变量 (ivar) | 真正存储数据的 "仓库",如 _name 。 |
自动合成时由编译器自动创建。 |
@property |
"广告牌",公开声明该类有某个属性,并承诺提供存取方法。 | 必须写,定义了属性的名称和特性。 |
@synthesize |
"实现工厂",负责生成存取方法的具体代码并关联实例变量。 | 绝大多数情况由编译器自动完成,无需手动编写。 |
对于新手来说,你只需要记住:
- 在
.h
文件中使用@property
来声明你的属性。 - 通常情况下,你不需要 在
.m
文件中写任何@synthesize
代码,编译器会为你处理好一切。 - 只有当你需要完全自定义 getter 和 setter 的行为时,才可能需要手动添加
@synthesize
来确保实例变量被创建。