@property介绍
相信做过iOS开发的同学都使用过@property,@property翻译过来是属性。在定义一个类时,常常会有多个@property,有了@property,我们可以用来保存类的一些信息或者状态。比如定义一个Student类:
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@end
Student类中有两个属性,分别是name和sex。
在程序中使用时,可以使用
self.name = @"xxx";
self.sex = @"xxx";
那么,为什么可以这样用呢?self.name是self的name变量嘛?还是其他的什么?属性中的copy代表什么?nonatomic呢?下面来看一下这些问题的答案。
@property 本质
@property到底是什么呢?实际上@property = 实例变量 + get方法 + set方法。也就是说属性
@property (nonatomic, copy) NSString *name;
代表的有实例变量,get方法和set方法。如果大家用过Java,相信对set方法和get方法应该很熟悉,这里的set、get方法和Java里面的作用是一样的,get方法用来获取变量的值,set方法用来设置变量的值。使用@property生成的实例变量、get方法、set方法的命名有严格的规范,实例变量的名称、get方法名、set方法名稍后再介绍。
这里需要注意的是,包括实例变量、get方法和set方法,不会真的出现在我们的编辑器里面,使用属性生成的实例变量、get方法、set方法是在编译过程中生成的。下面介绍一下set方法、get方法以及自动生成的实例变量。
setter方法
set方法也可以称为setter方法,之后看到setter方法直接理解成set方法即可。同理,get方法也被称为getter方法。
还是以上面的属性:
@property (nonatomic, copy) NSString *name;
为例,属性name生成的setter方法是
- (void)setName:(NSString *)name;
该命名方法是固定的,是约定成束的。如果属性名是firstName,那么setter方法是:
- (void)setFirstName:(NSString *)firstName;
项目中,很多时候会有重写setter方法的需求,只要重写对应的方法即可。比如说重写name属性的setter方法:
- (void)setName:(NSString *)name
{
NSLog(@"rewrite setter");
_name = name;
}
关于_name是什么,后续会介绍。
getter方法
以属性
@property (nonatomic, copy) NSString *name;
为例,编译器自动生成的getter方法是
- (NSString *)name;
getter方法的命名也是固定的。如果属性名是firstName,那么getter方法是:
- (NSString *)firstName;
重写getter方法:
- (NSString *)name
{
NSLog(@"rewrite getter");
return _name;
}
如果我们定义了name属性,并且按照上面所述,重写了getter方法和setter方法,Xcode会提示如下的错误:
Use of undeclared identifier '_name'; did you mean 'name'?
稍后我们再解释为何会有该错误,以及如何解决。先来看一下_name到底是什么。
实例变量
既然@property = 实例变量 + getter + setter,那么属性所生成的实例变量名是什么呢?根据上面的例子,也很容易猜到,项目中也经常使用,实例变量的名称就是_name。实例变量的命名也是有固定格式的,下划线+属性名。如果属性是@property firstName,那么生成的实例变量就是_firstName。这也是为何我们在setter方法和getter方法,以及其他的方法中可以使用_name的原因。
这里再提一下,无论是实例变量,还是setter、getter方法,命名都是有严格规范的。正是因为有了这种规范,编译器才能够自动生成方法,这也要求我们在项目中,对变量的命名,方法的命名遵循一定的规范。
自动合成
定义一个@property,在编译期间,编译器会生成实例变量、getter方法、setter方法,这些方法、变量是通过自动合成(autosynthesize)的方式生成并添加到类中。实际上,一个类经过编译后,会生成变量列表ivar_list,方法列表method_list,每添加一个属性,在变量列表ivar_list会添加对应的变量,如_name,方法列表method_list中会添加对应的setter方法和getter方法。
动态合成
既然有自动合成,那么相对应的就要有非自动合成,非自动合成又称为动态合成。定义一个属性,默认是自动合成的,默认会生成getter方法和setter方法,这也是为何我们可以直接使用self.属性名的原因。实际上,自动合成对应的代码是:
@synthesize name = _name;
这行代码是编译器自动生成的,无需我们来写。相应的,如果我们想要动态合成,需要自己写如下代码:
@dynamic sex;
这样代码就告诉编译器,sex属性的变量名、getter方法、setter方法由开发者自己来添加,编译器无需处理。
那么这样写和自动合成有什么区别呢?来看下面的代码:
Student *stu = [[Student alloc] init];
stu.sex = @"male";
编译,不会有任何问题。运行,也没问题。但是当代码执行到这一行的时候,程序崩溃了,崩溃信息是:
[Student setSex:]: unrecognized selector sent to instance 0x60000217f1a0
即:Student没有setSex方法,没有属性sex的setter方法。这就是动态合成和自动合成的区别。动态合成,需要开发者自己来写属性的setter方法和getter方法。添加上setter方法:
- (void)setSex:(NSString *)sex
{
_sex = sex;
}
由于使用@dynamic,编译器不会自动生成变量,因此除此之外,还需要手动定义_sex变量,如下