synthesize的意思是 "合成".
Xcode4时,@property只能在.h中生成getter、setter方法的声明, 需要在.m中手动加上@synthesize,才会有setter\getter的实现,以及对应的变量_property;
xcode4之前 :
property = setter方法声明 + getter方法声明
synthesize = ivar + setter实现 + getter实现
从Xcode5开始, 编译器有了自动合成机制(Auto property synthesis),只写@property就可以自动生成_property成员变量和getter、setter方法的声明和实现, 不需要写synthesize了。
xcode5之后, 其实property和synthesize的职责没变,只是编译器会默认添加synthesize, 真实的情况还是 :
property = setter方法声明 + getter方法声明
默认添加的synthesize = ivar + setter实现 + getter实现
---->看起来变成了 property = ivar + setter(声明+实现) + getter(声明+实现)
自动合成机制(Auto property synthesis) :如果我们既没有写synthesize
也没有写dynamic
,那编译器默认会为我们添加:
@synthesize property = _property; //如果不存在_property,则会创建一个_property成员变量,如果存在,则不会添加成员变量。
因此在类内部我们可以使用 _property
来进行赋值、取值操作。
现在我们大多数情况下都是直接创建一个property来使用,背后由编译器来为我们自动添加上面的synthesize
,进而读取synthezise
语义添加 setter和getter函数。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码.
synthesize还是有使用场景的.
场景1 : 同时重写setter/getter
以上2种情况下, 自动合成就失效了, 无法对应到默认的变量_property,需要手动定义一个变量_property或者使用@synthesize property = _property; 指定一个变量来绑定到属性上。(比较常见的场景)
什么情况下自动合成会失效 ?
同时重写了属性的setter和getter时;
重写了只读属性的getter时;
使用了@dynamic时;
在 @protocol 中定义的所有属性;
在 category 中定义的所有属性;
父类已有的属性, 子类重载的属性不会自动合成;
场景2 : 指定属性对应的ivar,修改生成的成员变量名字。不推荐
synthesize还有一个作用就是给属性对应的成员变量起一个不同的名字, @synthesize property = _newName;
这样做之后, 在类中只能使用_newName/self.property来存取值, _property不可用。(奇怪的用法, 一般不这么用, 可以直接给属性起名为newName)
场景3 : 修改父类属性的读写权限,不推荐
网上有看到使用synthesize改变父类的读写权限的.其实是不可以的.https://www.jianshu.com/p/99abb1fca7ec
父类有一个只读的属性,比如name, 子类想修改这个name的值, 然后在子类的.m中 写了@synthesize name = _name;然后子类在合适的地方进行修改.
看上去没有问题, 但是编译器实际生产了2个_name变量, 一个是父类的_name变量, 一个是子类合成的_name, (如果真的需要修改父类的只读属性值 ,可以通过kvc修改)
场景4 : 实现了带有property属性的protocol
协议中可以写属性, 但是不会ivar. 以下图的系统协议为例, 我们写的类遵守了协议, xcode就会报警告说需要添加 @synthesize beginTime ; 添加之后警告消除. (这种场景不需要特别关注, 根据xcode提示处理即可.)
场景5 : 重载父类的属性,不推荐
如果我们在子类中重载了父类的属性,那在子类中我们必须@synthesize来手动合成ivar。如下图所示:
会出现警告,告诉你自动合成机制不会自动合成name属性,已经被父类实现了setter/getter方法,使用@dynamic来自己手动实现。
下面给出解决方式:
方式1、手动添加 @synthesize name = _name; 我知道父类写了, 但是我自己也要有, 再生成一个_name, 这样的话其实在一个teacher中就有了2个_name, 一个是父类的_name, 一个是子类的_name, 父类的某些方法使用self.name=@"xxx"可能不经意间就修改了子类的_name, 是不是很魔幻, 改bug的时候就懵B了. 这种方式不推荐.
方式2、既然有2个_name, 那我利用synthesize给属性指定另一个ivar, 我这样写 @synthesize name = _teacherName; 父类我不管了, 子类起个名字叫_teacherName, 这样父类就区分出来了, 也没有重名风险了. 父类里使用_name是安全的了, 但是还是那个风险(除非父类的属性是readonly的,不然这个风险无法消除), 父类的方法里使用self.name=@"xxx", 此时setter/getter方法会从teacher的方法列表中找, 结果找到了, 然后_teacherName还是被修改了, 子类的属性还是在不知情的情况下被父类修改. 不推荐
方式3、既然synthesize不行了 , 那我听系统的建议使用@dynamic name ; 此时teacher的内存布局只有一个_name了, 子类父类修改self.name也是针对同一个属性了. 适合子类父类用同一个值存储变量的情况, 如果这种应用场景, 那么子类.h中的属性其实也没有必要了, 可以删掉了.
方式4、行了行了, 这么折腾干啥, 既然父类已经有这个属性了, 但是我还需要用属性存不同的变量, 我在起一个新名字多好, 这种场景适合子类父类存的是不同的值, 只是凑巧命名的时候重复了而已.
@property (nonatomic, copy) NSString *teacherName;
总结一下
synthesize
到底做了些什么呢?主要做了2件事:
setter/getter
方法实现。synthesize的使用场景, 我们
大多数情况下直接创建一个property来使用, 不需要做额外的操作.上面写到的5个场景, 其实只有2个是必要的, 其他的场景可以通过其他更好的方式解决. 如非必要,勿增实体.
synthesize添加成员变量.
@dynamic
告诉编译器,不要为property
声明的属性添加setter/getter方法, 由用户自己实现,该属性的getter和setter方法可能不在本类,而在其他地方(比如父类或者在运行时中提供, 如果不实现的话, 运行时会有Unrecoginzed Selector Crash), 可以防止自己的方法被覆盖.
所以用到@dynamic
的地方很少,那么在什么情况下会使用到呢?
动态生成类和变量的情景中, 有些访问器方法是在运行时动态创建的,例如CoreData的NSManagedObject类中使用的某些访问器。如果希望在这些情况中声明和使用属性,但希望避免在编译时出现方法的警告,可以使用@dynamic指令而不是@synthesize。
使用@dynamic指令本质上是告诉编译器“不要担心,方法马上会出现”,使用了动态绑定的概念。