属性自动合成

Clang 编译器提供了 OC 自动合成属性的功能。如果一个属性没有被声明为 @dynamic 或者开发者没有自定制它的 getter 或者 setter 方法实现。那 Clang 会自动为你合成 gettersetter 方法的实现同时生成对应的成员变量,检查 Clang 编译器是否支持自动合成使用__has_feature(objc_default_synthesize_properties) 这个条件判断。举例

#if __has_feature(objc_default_synthesize_properties)
    //support autosynthesis
#else
    //not support
#endif

当编译器不能自动为你合成属性的时候,需要开发者手动进行。其实也可以换个说法,什么时候需要开发者手动 @synthesize

举个例子,开发中偶尔会遇到这种报错的情况:

@property (nonatomic,strong) NSString *name;
.....
@implementation
- (void)setName:(NSString *)name {
    _name = name; #编译错误:Use of undeclared identifier '_name'; did you mean 'name'?
}
-  (NSString *)name {
    return @"";
}
@end

这个时候需要我们手动的添加一行代码 @synthesize name = _name; 编译错误就会消失。或者你把编译错误的那行去掉,然后使用命令 clang -rewrite-objc main.m 编译原文件,发现生成的对象结构体中并没有 name 对应的成员变量。

接下来就是总结什么场景下需要我们手动添加 @synthesize

  1. 就是我们上面 demo 展示的这种可读写的属性,但是开发者自定义了 gettersetter 方法,注意必须是同时复写,如果只复写 setter 或者 getter 的话是不会出现问题的。这也是 clang 编译器不支持的自动合成的场景。
  2. 只读属性,开发者自定义了 getter 方法。
  3. @dynamic 修饰的属性,@dynamic 本质是告诉编译器 settergetter 方法由开发者自己定义,不自动合成。
  4. 在协议 @protocol 中声明的属性。
  5. 在类别 @category 中声明的属性。这是因为类别不支持自动添加成员变量,需要手动进行引用关联。
  6. 如果你复写了父类的属性,你也需要显式地添加 @synthesize
  7. 如果你想手动修改成员变量名的话,可以使用 @synthesize 修改,比如默认成员变量名是 _name 使用 @synthesize name = yname; 修改成员变量名为 yname,但是不建议这么做。

2018-03-23

补充一些关于第 6 点的说明。

如果父类自动生成属性成员变量和 getter, setter 方法。子类对父类的属性重新进行了 @synthesize 那将会生成新的实例变量。

@interface Fan : NSObject
@property (nonatomic, strong) NSString *name;
@end
//父类 Fan 并没有 @synthesize 而且没有复写 getter 和 setter 方法.
......
@interface FanSub : Fan
@property (nonatomic, strong) NSString *name;
....
@implementation FanSub
@synthesize name = fname;
- (void)setName:(NSString *)name {
    fname = name;
}
- (NSString *)name {
    return fname;
}
@end

注意的是此时子类中的 name 属性对应的实例变量其实是 fname. 即它自己生成的实例变量,而不是父类的 _name 了。可以通过查看编译后的选项来验证 clang -rewrite-objc main.m。结合编译后对应的结构体看一下,确实是成员变量发生了变化。所以这个时候要注意,不能把子类的属性成员变量和父类的成员变量混为一谈。

//编译后的结构体.
struct Fan_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
struct FanSub_IMPL {
	struct Fan_IMPL Fan_IVARS;
	NSString *fname;
};

一点总结: xxx

所以子类合成和使用属性对应自动变量的两个原则:

  1. 不允许合成和父类名字一样自动变量
  2. 如果在子类中有用到父类属性对应的成动变量,则需要显式地在父类头文件的成员变量区域去进行自动变量的声明。

# 一些 QA

Q:@synthesize 合成实例变量的规则是什么?假如 property 名为 foo,存在一个名为_foo 的实例变量,那么还会自动合成新变量么? A: 默认合成带属性名字前面带下划线的成员变量。如果这个下划线的成员变量已经存在,就不会合成新的成员变量了。即上面问题答案是不会合成新的成员变量了。

#参考地址#

  1. When should I use @synthesize explicitly? (opens new window)
  2. Objective-C Autosynthesis of Properties (opens new window)
  3. @synthesize 和 @dynamic 的作用 (opens new window)