第十一章、属性

1使用属性值 我们转换了一个比较简单的类(AllWeatherRadial)以使用属性。我们假设有人从不同的商店购买四个降价销售的轮胎,因此,这四个轮胎具有不同的性能值

//main()函数 Car *car = [[Car alloc] init]; for(int i = 0; i < 4; i++) { AllWeatherRadial *tire; tire = [[AllWeatherRadial alloc] init]; [tire setRainHanding:20+i]; [tire setSnowHanding:28+i]; NSLog(@"tire %'s handling is %.f%.f",i,[tire rainHandling]; [tire snowHandling]); [car setTire:tire atIndex:i]; } Engine *engine = [[Slant6 alloc] init]; [car setEngine:engine]; [car print];

第十一章、属性
文章图片
1.1简化接口代码
现在我们来研究一下,AllWeatherRadial类的接口代码
@interface AllWeatherRadial:Tire { float rainHandling; float snowHandling; } -(void)setRainHandling:(float)rainHandling; -(float)rainHandling; -(void)setSnowHandling:(float)snowHandling; -(float)snowHandling; @end

这种写法已经过时,我们将其改写为具有属性风格的新形式
@interface AllWeatherRadial:Tire { float rainHandling; float snowHandling; } @property float rainHandling; @property float snowHandling; @end

  • @property是一种新的编译器功能,它意味着声明了一个新对象的属性
  • @property float rainHandling;这句话表明AllWeatherRadial类的对象具有float类型的属性,其名称为rainHandling。
  • 而且你也可以通过调用-setRainHandling:来设置属性,通过-rainHandling来访问属性
  • @property预编译指令的作用是自动声明属性的setter和getter方法
1.2简化实现代码
AllWeatherRadial的简化实现
@implementation AllWeatherRadial @synthesize rainHandling; @synthesize snowHandling; -(id)initWithPressure:(float) p treadDepth:(float) td{ if(self = [super initWithPressure:p treadDepth:td]){ rainHandling = 23.7; snowHandling = 42.5; } return self; } -(NSString *)description{ NSString *desc; desc = [[NSString alloc] initWithFormat:@"AllweatherRadial:%.1f / %.1f / %.1f / %.1f",[self pressure],[self treadDepth],[self rainHandling],[self snowHandling]]; return desc; }

  • @synthesize也是一种新的编译器功能,它表示“创建了该属性的访问代码”(Xcode4.5之后可以不必使用@synthesize)
  • 当遇到@synthesize rainHandling; 这行代码时,编译器将添加实现-setRainHandling:和-rainHandling方法的预编译代码
  • 所有的属性都是基于变量的,所以在你合成setter和getter方法的时候,编译器会自动创建与属性名称相同的实例变量
  • 我们的头文件有两个叫做rainHandling和snowHandling的实例变量,如果你没有声明这些变量,编译器也会声明的
  • 有两个地方可以用来添加实例变量声明,头文件和声明文件。假设你有一个子类,并且你想要从子类直接通过属性访问变量,这种情况下,语句必须放在头文件中。如果是当前类访问,则可以放在声明文件中
修改后的代码
//接口 @interface AllWeatherRadial:Tire @property float rainHandling; @property float snowHandling; @end //实现 @implementation AllWeatherRadial @synthesize rainHandling; @synthesize snowHandling; -(id)initWithPressure:(float) p treadDepth:(float) td{ if(self = [super initWithPressure:p treadDepth:td]){ rainHandling = 23.7; snowHandling = 42.5; } return self; } -(NSString *)description{ NSString *desc; desc = [[NSString alloc] initWithFormat:@"AllweatherRadial:%.1f / %.1f / %.1f / %.1f",[self pressure],[self treadDepth],[self rainHandling],[self snowHandling]]; return desc; }

1.3点表达式的妙用
回想一下我们在main函数中添加的用于修改轮胎性能值得两行代码
[tire setRainHandling:20+i]; [tire setSnowHandling:28+i];

我们可以将其替换为下面的代码
tire.rainHandling:20+i; tire.snowHandling:28+i;

如果点表达式出现在了=的左边,该变量名称的setter方法将被调用
如果点表达式出现在了=的右边,该变量名称的getter方法将被调用
2.属性扩展 我们使用访问方法来访问对象时需要保留和释放对象。对于某些对象的值,尤其是字符串的值,你总是会复制(-copy)它们。而对于其他对象的值,如委托(后面会说)你根本不会想要保留它们。

第十一章、属性
文章图片

下面给Car类添加一种新的特性,这样我们就可以使用一些新的语法了。
我们在Car对象中添加一个新的实例变量name
#import @interface Tire : NSObject @end @implementation Tire - (NSString *) description{ return (@"a tire"); } @end//Tire @interface Engine : NSObject @end @implementation Engine - (NSString *) description{ return (@"a engine"); } @end//Engine @interface Car : NSObject{ NSString *name; Engine *engine; Tire *tires[4]; } -(void)setName:(NSString *)newName; -(NSString *)name; -(void)print; @end @implementation Car -(id)init{ if (self = [super init]) { engine = [Engine new]; tires[0] = [Tire new]; tires[1] = [Tire new]; tires[2] = [Tire new]; tires[3] = [Tire new]; } return self; } -(void)setName:(NSString *)newName{ name = [newName copy]; } -(NSString *)name{ return name; } -(void)print{ NSLog(@"%@ has:",name); NSLog(@"%@",engine); NSLog(@"%@",tires[0]); NSLog(@"%@",tires[1]); NSLog(@"%@",tires[2]); NSLog(@"%@",tires[3]); } @end//Car int main(int argc, const char * argv[]) { @autoreleasepool{ Car *car = [[Car alloc] init]; [car setName:@"Herbie"]; [car print]; } return 0; } //运行结果 2018-10-28 09:24:11.474592-0700 Car-New[981:120418] Herbie has: 2018-10-28 09:24:11.474825-0700 Car-New[981:120418] a engine 2018-10-28 09:24:11.475023-0700 Car-New[981:120418] a tire 2018-10-28 09:24:11.475049-0700 Car-New[981:120418] a tire 2018-10-28 09:24:11.475058-0700 Car-New[981:120418] a tire 2018-10-28 09:24:11.475065-0700 Car-New[981:120418] a tire Program ended with exit code: 0

我们开始想Car类添加属性
#import @interface Tire : NSObject @end @implementation Tire - (NSString *) description{ return (@"a tire"); } @end//Tire @interface Engine : NSObject @end @implementation Engine - (NSString *) description{ return (@"a engine"); } @end//Engine @interface Car : NSObject{ Engine *engine; Tire *tires[4]; } @property (copy) NSString *name; -(void)print; @end @implementation Car -(id)init{ if (self = [super init]) { engine = [Engine new]; tires[0] = [Tire new]; tires[1] = [Tire new]; tires[2] = [Tire new]; tires[3] = [Tire new]; } return self; } @synthesize name; -(void)print{ NSLog(@"%@ has:",name); NSLog(@"%@",engine); NSLog(@"%@",tires[0]); NSLog(@"%@",tires[1]); NSLog(@"%@",tires[2]); NSLog(@"%@",tires[3]); } @end//Car int main(int argc, const char * argv[]) { @autoreleasepool{ Car *car = [[Car alloc] init]; car.name = @"Herbie"; [car print]; } return 0; } //运行结果 2018-10-28 09:24:11.474592-0700 Car-New[981:120418] Herbie has: 2018-10-28 09:24:11.474825-0700 Car-New[981:120418] a engine 2018-10-28 09:24:11.475023-0700 Car-New[981:120418] a tire 2018-10-28 09:24:11.475049-0700 Car-New[981:120418] a tire 2018-10-28 09:24:11.475058-0700 Car-New[981:120418] a tire 2018-10-28 09:24:11.475065-0700 Car-New[981:120418] a tire Program ended with exit code: 0

访问方法的说明已经被@property声明所取代,也没有了实例变量name的声明,主函数中的点表示法。
第十一章、属性
文章图片
2.1名称的使用
  • 属性的名称石中玉属性的实例变量名称相同,这种情况非常普遍
  • 不过有时候,你也希望实例变量是一个名称,而公开的属性是另一个名称
只需要在@interface Car : NSObject中修改该实例变量的名称
@interface Car : NSObject{ NSString *appellation; } @property (copy) NSString *name; @end

再修改@synthesize指令
@synthesize name = appellation;

  • 编译器将创建-setName和-name方法,但在实现代码中用的却是appellation实例变量
第十一章、属性
文章图片
2.2只读属性
  • 默认情况下,属性是可变的(你可以读取也可以修改,readwrite,一般不写)。
  • 如果你想要你的属性只可以被读,不可以被修改,你可以对这个@property使用readonly特性
//鞋码和驾驶证号码 @interface Me : NSObject{ float shoesSize; NSString *licenseNumber; } @property (readonly) float shoesSize; @property (readonly) NSString *licenseNumber; @end

  • 当编译器知道这个@property属性是可读的,它将只生成getter方法而不会生成setter方法
2.3自己动手有时更好
  • 我自己来
如果你想要你的属性不要生成任何代码或者创建相应的实例变量,你可以用关键字@dynamic
//添加bodyMassIndex-身体质量指数属性 @property (readonly) float bodyMassIndex; @dynamic bodyMassIndex - (float) bodyMassIndex{ //compute and return bodyMassIndex }

【第十一章、属性】上面的例子就是告诉编译器不要创建变量或者getter方法---我们自己来
  • 我不喜欢这个方法名
    第十一章、属性
    文章图片
2.4特性不是万能的
第十一章、属性
文章图片

    推荐阅读