00-1010我相信@property已经在iOS开发中使用了,@property翻译为property。定义一个类时,通常有多个@属性。使用@property,我们可以保存类的一些信息或状态。例如,定义一个学生类:
@学生界面:对象
@property(非原子,副本)NSString * name
@property(非原子,复制)NSString * sex
@end
学生类有两个属性,名字和性别。
在程序中使用时,您可以使用
self.name=@ ' xxx
self.sex=@ ' x
那么,为什么可以这样使用呢?属性中的copy和nonatomic分别代表什么?让我们一起来看看这些问题。
00-1010实际上@property=实例变量get方法set方法。也就是说,属性
@property(非原子,副本)NSString * name
表示实例变量、获取方法和设置方法。get方法用于获取变量的值,set方法用于设置变量的值。由@property生成的实例变量、get方法和set方法的命名是严格规定的。实例变量、get方法和set方法的名称将在后面介绍。
这里需要注意的是,实例变量、get方法和set方法不会真正出现在我们的编辑器中。通过使用属性生成的实例变量、get方法和set方法都是在编译过程中生成的。下面介绍set方法、get方法和自动生成的实例变量。
@property介绍
set方法也可以称为setter方法,然后可以看到setter方法可以直接理解为set方法。同样,get方法也被称为getter方法。或者具有上述属性:
@property(非原子,副本)NSString * name
例如,由属性名生成的setter方法是
- (void)setName:(NSString *)名称;
命名方式固定,同意捆绑。如果属性名是名字,那么setter方法是:
-(void)setfirst name :(NSString *)first name;
在项目中,经常需要重写setter方法,只需重写相应的方法即可。例如,重写name属性的setter方法:
- (void)setName:(NSString *)名称
{
NSLog(@ '重写setter ');
_ name=name
}
什么是_name将在后面介绍。
00-1010带属性
@property(非原子,副本)NSString * name
例如,编译器自动生成的getter方法是
- (NSString *)名称;
getter方法的命名也是固定的。如果属性名是名字,那么getter方法是:
- (NSString *)名字;
重写getter方法:
- (NSString *)名称
{
NSLog(@ '重写getter ');
return _ name
}
如果我们如上所述定义name属性并重写getter方法和setter方法,Xcode将提示以下错误:
使用未声明的标识符“_ name”;做
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变量,如下:
@interface Student : NSObject { NSString *_sex; } @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *sex; @end现在再编译,运行,执行没有错误和崩溃。
重写setter、getter方法的注意事项
上面的例子中,重写了属性name的getter方法和setter方法,如下:
- (void)setName:(NSString *)name { NSLog(@"rewrite setter"); _name = name; } - (NSString *)name { NSLog(@"rewrite getter"); return _name; }但是编译器会提示错误,错误信息如下:
Use of undeclared identifier '_name'; did you mean 'name'?提示没有_name变量。为什么呢?我们没有声明@dynamic,那默认就是@autosynthesize,为何没有_name变量呢?奇怪的是,倘若我们把getter方法,或者setter方法注释掉,gettter、setter方法只留下一个,不会有错误,为什么呢?
还是编译器做了些处理。对于一个可读写的属性来说,当我们重写了其setter、getter方法时,编译器会认为开发者想手动管理@property,此时会将@property作为@dynamic来处理,因此也就不会自动生成变量。解决方法,显示的将属性和一个变量绑定:
@synthesize name = _name;这样就没问题了。如果一个属性是只读的,重写了其getter方法时,编译器也会认为该属性是@dynamic,关于可读写、只读,下面会介绍。这里提醒一下,当项目中重写了属性的getter方法和setter方法时,注意下是否有编译的问题。
修改实例变量的名称
使用自动合成时,针对
@property (nonatomic, copy) NSString *name;属性,生成的变量名是_name。倘若,不习惯使用下划线开头的变量名,能否指定属性对应的变量名呢?答案是可以的,使用的是上面介绍过的@synthesize关键字。如下:
@synthesize name = stuName;这样,name属性生成的变量名就是stuName,后续使用时需要写stuName,而不是_name。如getter、setter方法:
- (void)setName:(NSString *)name { NSLog(@"rewrite setter"); stuName = name; } - (NSString *)name { NSLog(@"rewrite getter"); return stuName; }注意:虽然可以使用@synthesize关键字修改变量名,但是如无特殊需求,不建议这样做。因为默认情况下编译器已经为我们生成了变量名,大多数的项目、开发者也都会遵循这样的规范,既然苹果已经定义了一个好的规范,为什么不遵守呢?
getter方法中为何不能用self
有经验的开发者应该都知道这一点,在getter方法中是不能使用self.的,比如:
- (NSString *)name { NSLog(@"rewrite getter"); return self.name; // 错误的写法,会造成死循环 }原因代码注释中已经写了,这样会造成死循环。这里需要注意的是:self.name实际上就是执行了属性name的getter方法,getter方法中又调用了self.name, 会一直递归调用,直到程序崩溃。通常程序中使用:
self.name = @"aaa";这样的方式,setter方法会被调用。