@synthesize到底对属性干了什么, 使用场景总结

synthesize的意思是 "合成". 

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 

  • 当同时重写一个属性的getter和setter两个方法的时候,
  • 或者属性是readonly时, 重写了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修改)

@synthesize到底对属性干了什么, 使用场景总结_第1张图片

场景4 : 实现了带有property属性的protocol

协议中可以写属性, 但是不会ivar.   以下图的系统协议为例, 我们写的类遵守了协议, xcode就会报警告说需要添加 @synthesize beginTime ; 添加之后警告消除. (这种场景不需要特别关注, 根据xcode提示处理即可.)

@synthesize到底对属性干了什么, 使用场景总结_第2张图片

@synthesize到底对属性干了什么, 使用场景总结_第3张图片

@synthesize到底对属性干了什么, 使用场景总结_第4张图片

场景5 : 重载父类的属性,不推荐

如果我们在子类中重载了父类的属性,那在子类中我们必须@synthesize来手动合成ivar。如下图所示:

会出现警告,告诉你自动合成机制不会自动合成name属性,已经被父类实现了setter/getter方法,使用@dynamic来自己手动实现。
下面给出解决方式:
方式1、手动添加 @synthesize name = _name;  我知道父类写了, 但是我自己也要有, 再生成一个_name, 这样的话其实在一个teacher中就有了2个_name, 一个是父类的_name, 一个是子类的_name, 父类的某些方法使用self.name=@"xxx"可能不经意间就修改了子类的_name, 是不是很魔幻, 改bug的时候就懵B了. 这种方式不推荐.

@synthesize到底对属性干了什么, 使用场景总结_第5张图片

方式2、既然有2个_name, 那我利用synthesize给属性指定另一个ivar, 我这样写 @synthesize name = _teacherName; 父类我不管了, 子类起个名字叫_teacherName, 这样父类就区分出来了, 也没有重名风险了. 父类里使用_name是安全的了, 但是还是那个风险(除非父类的属性是readonly的,不然这个风险无法消除), 父类的方法里使用self.name=@"xxx", 此时setter/getter方法会从teacher的方法列表中找, 结果找到了, 然后_teacherName还是被修改了, 子类的属性还是在不知情的情况下被父类修改. 不推荐

@synthesize到底对属性干了什么, 使用场景总结_第6张图片

方式3、既然synthesize不行了 , 那我听系统的建议使用@dynamic name ;  此时teacher的内存布局只有一个_name了, 子类父类修改self.name也是针对同一个属性了. 适合子类父类用同一个值存储变量的情况, 如果这种应用场景, 那么子类.h中的属性其实也没有必要了, 可以删掉了.

@synthesize到底对属性干了什么, 使用场景总结_第7张图片

方式4、行了行了, 这么折腾干啥, 既然父类已经有这个属性了, 但是我还需要用属性存不同的变量, 我在起一个新名字多好,  这种场景适合子类父类存的是不同的值, 只是凑巧命名的时候重复了而已.

@property (nonatomic, copy) NSString *teacherName;

总结一下 

synthesize到底做了些什么呢?主要做了2件事:

  • 生成私有成员变量(只能在本类访问,外界无法通过_property访问, 外界可以通过setter/getter访问)。可以改变这个成员变量的名字。 如果不存在_property,则会创建一个_property成员变量,如果存在,则不会添加成员变量。
  • 根据readwrite / readonly 为属性生成setter/getter方法实现。

synthesize的使用场景, 我们大多数情况下直接创建一个property来使用, 不需要做额外的操作.上面写到的5个场景, 其实只有2个是必要的, 其他的场景可以通过其他更好的方式解决.   如非必要,勿增实体.

  • 同时重写setter/getter导致自动合成失效时, 使用synthesize添加成员变量.
  • 协议中带有属性, 自定义类遵守了协议, 使用synthesize消除警告并生成协议中的成员变量.

 

题外话,什么是@dynamic?

@dynamic 告诉编译器,不要为property声明的属性添加setter/getter方法, 由用户自己实现,该属性的getter和setter方法可能不在本类,而在其他地方(比如父类或者在运行时中提供, 如果不实现的话, 运行时会有Unrecoginzed Selector Crash), 可以防止自己的方法被覆盖.

所以用到@dynamic的地方很少,那么在什么情况下会使用到呢?

动态生成类和变量的情景中, 有些访问器方法是在运行时动态创建的,例如CoreData的NSManagedObject类中使用的某些访问器。如果希望在这些情况中声明和使用属性,但希望避免在编译时出现方法的警告,可以使用@dynamic指令而不是@synthesize。

使用@dynamic指令本质上是告诉编译器“不要担心,方法马上会出现”,使用了动态绑定的概念。

 

你可能感兴趣的:(零碎知识点)