Apple在Objective-C 2.0中引入了属性(Property),它组合了新的预编译指令和新的属性访问器语法。
新的属性功能可以显著减少必须编写的代码行数,让代码看起来更干练,也可减少出现 Bug的机会。
0x01 使用属性值
使用property属性,编译器会自动生成属性的setter / getter方法,还会自动生成对应加下划线的实例变量名。
由于自定义的变量名跟获取函数名一样,为了区分,实际的变量名在前面加下划线。
虽然默认是加下划线,但可以在实现文件中使用关键词@synthesize自定义实际的变量名。
加下划线的变量名由于是编译过程中生成的,所以我们看不到,但事实存在。
所以,我们的AllWeatherRadial类接口代码可以大大简化:
//Original
#import <Foundation/Foundation.h>
#import "Tire.h"
@interface AllWeatherRadial : Tire {
float rainHandling;
float snowHandling;
}
//手动编写的setter/getter方法
- (void) setRainHandling: (float) rainHanding;
- (float) rainHandling;
- (void) setSnowHandling: (float) snowHandling;
- (float) snowHandling;
@end
//---------------------------------------------------------------------------------------
//Using @property
#import <Foundation/Foundation.h>
#import "Tire.h"
@interface AllWeatherRadial : Tire
//使用属性风格声明rainHandling和snowHandling的属性,并删除之前的setter/getter方法
@property float rainHandling;
@property float snowHandling;
@end
0x02 实际存在的下划线变量名
@property 在Xcode 4.5版本后得到了增强,一行代码就可以完成setter / getter方法的声明和实现。
但是当我们重写了setter / getter方法或者在实现其他方法的过程中使用到之前未使用属性风格声明的变量就会报错。
比如指定初始化方法的报错,找不到变量:
原因在于,rainHandling和snowHandling已被声明为属性,现在的成员变量名是_rainHandling和_snowHandling。
我们操作的目标是成员变量。
很简单,把报错的属性名前面加上下划线变成成员变量名即可(证明下划线变量名事实存在):
@implementation AllWeatherRadial
- (id) initWithPressure:(float)p
treadDepth:(float)td
{
if (self = [super initWithPressure: p
treadDepth: td]) {
_rainHandling = 24.7;
_snowHandling = 42.5; //添加属性后,必须使用下划线变量名
}
return (self);
} // initWithPressure:treadDepth
0x03 点表达式仍然适用
在AllWeatherRadial.h中有个description方法,使用属性后,点表达式仍然不需要做修改。
通过点语法访问变量,等效于调用自动生成的存取方法访问变量:
- 如果点表达式出现在等号左边,调用的是变量的setter方法;
- 如果点表达式出现在等号右边,调用的是变量的getter方法。
- (NSString *) description
{
NSString *desc;
desc = [[NSString alloc] initWithFormat:
@"AllWeatherRadial: %.1f / %.1f / %.1f / %.1f",
self.pressure, self.treadDepth, self.rainHandling, self.snowHandling];
//点表达式不需要修改
return (desc);
} // description
@end // AllWeatherRadial
0x04 自定义变量名
如果我们觉得_rainHandling和_snowHandling名字太长了,多个下划线用起来也不顺手,可以自定义变量名。
@synthesize 编译器指令可以达到该目的:
#import "AllWeatherRadial.h"
@implementation AllWeatherRadial
//自定义变量名
@synthesize rainHandling = rain;
@synthesize snowHandling = snow;
- (id) initWithPressure:(float)p
treadDepth:(float)td
{
if (self = [super initWithPressure: p
treadDepth: td]) {
rain = 24.7;
snow = 42.5; //这里就能用自定义变量名了
}
return (self);
} // initWithPressure:treadDepth
在Xcode 4.5版本之前,@synthesize的和@property成对出现来自动合成指定属性变量的存取方法的。
但实际上为了减少不必要的麻烦,用@synthesize自定义实例变量名的行为是不被建议的。
因为有很多程序员习惯上会调用编译器生成的下划线变量名,如果自定义了名称,依赖于该头文件的代码均会报错。
但使用self指针的点方法仍然有效,因为self始终指向调用此方法的当前对象。
0x05 禁止存取方法自动合成
@dynamic编译器指令相当于告诉编译器:“参数的getter和setter方法并不在此处,而在其他地方实现了或者生成了,当你程序运行的时候你就知道了,所以别警告我了。”
这样程序在运行的时候,对应参数的setter / setter方法就会在其他地方去寻找,比如超类里。
但如果声明了dynamic属性,却实际上没有对应的setter / setter方法,编译时可能没有异常,程序实际运行中碰到需要调用setter / setter方法的地方将崩溃,因为调用了不存在的方法。
@interface father()
@property (nonatomic, strong, readwrite) NSObject *sampleObject;
...
@synthesize sampleObject = _sampleObject;
@end
//------------------------------------------------------------------------
@interface child : father
@property (nonatomic, strong, readwrite) NSObject *sampleObject;
...
@dynamic sampleObject;
@end