Objective-C 初始化对象

对象初始化
 
两种不同方法创建对象:
1、是[类名 new],2、[[类名 alloc] init].这两种方法是等价的,不过Cocoa惯例使用alloc和init而不使用new。
 
分配对象
分 配(allocation)是一个新对象诞生的过程。向某个类发送alloc消息,就能为类分配一块足够大的内存,以存放该类的全部实例变量。同时 alloc方法还顺便将这块内存区域全部初始化为0,。不用担心由于初始化内存而引起各种随机bug。所有的BOOL类型变量被初始化为NO,所有int 类型变量被初始化为0,所有的float类型变量被初始化为0.0,所有的指针被初始化为nil。
 
刚刚分配的对象并不能立即使用,需要初始化,然后才能使用。Objective-C将这两种操作拆分为两个明确的步骤:分配和初始化。
Car *car = [Car alloc];
这样代码可以运行,但由于未初始化,会出现bug。
 
初始化对象
 
与分配对应的操作是初始化。初始化从操作系统取得一块内存用于存储对象。init方法(即执行初始化操作的方法)一般都会返回其正在初始化的对象。应该像下面这样嵌套调用alloc和init方法:
Car *car = [[Car alloc] init];
而不是这样:Car *car = [Car alloc]; [car init];
这种嵌套调用技术非常重要,因为初始化方法返回的对象可能与分配的对象不同。
 
 
编写初始化方法:
早期的CarParts类的init方法:
(id) init
{
     if (self = [super init]) {
          ....
     }
 
     return (self);
}
 
if (self = [super init]) {这行代码意味着self可能发生了改变。该声明中最先运行的代码是[super init],其作用是让超类完成其自身的初始化工作。
像这样的init方法可能会返回完全不同的对象。请记住,self参数是通过固定的距离寻找实例变量所在的内存位置的。 如果从init方法返回一个新对象,则需要更新self,以便其后的实例变量的引用可以被映射到正确的内存位置。这也是需要使用self = [super self]这种形式进行赋值的原因。记住这个赋值操作只影响该init方法中的self的值。而不影响该方法范围以外的任何内容。
 
如 果在初始化一个对象时出现问题,则init方法可能会返回nil,表明未能初始化该对象。如果从[super init]返回的值是nil,则if(self = [super init])的判断不会让主体代码执行。像这样将赋值和检查是否为空值结合起来是一种典型的C语言风格。
 
获得对象并使其运行的代码位于if语句正文部分的一对花括号里面。从内存管理的角度来看,这段代码是完全正确的,因为通过new方法返回的对象在开始运行时保留计数器的值被设置为1.
 
最后一行代码是'return (self);"。init方法返回了刚刚已经被初始化的对象。我们已经将[super init]返回值赋值给了self,这正是应该返回的值。
 
初始化时要做什么:
在这里,你要执行全新的初始化工作,给实例变量赋值并创建你的对象进行工作时所需的其他对象。在编写自己的init方法时,你必须确定在该办法中希望完成多少工作。
 
两种不同初始化方式:
 
第一种方式使用init方法创建了engine对象和全部的4个tire对象。这种方式使Car类变得可以“出产即用”(调用完alloc和init方法后就可以用来工作了)。
而另一种方式中,我们在init方法中不创建任何对象,只为engine对象和tire对象预留位置。创建了Car对象的方法还必须负责创建其中的engine对象和tire对象,并通过访问方法为其赋值。
 
 
便利初始化函数:
有些对象拥有多个以init开头的方法名。需要记住,这些init方法实际上没什么特别的,只是遵循命名约定的普通方法。
 
许多类包含便利初始化函数(convenience initializer),它们是用来完成某些额外工作的初始化方法,可以减轻你的负担。
 
NSString的一些初始化方法示例:
 
-(id) init;--这一基本方法初始化一个新的空字符串。对于不可变的NSString类来说这个方法没有多大用处。不过,你可以分配和初始化一个新的NSMutableString类的对象并开始向该类中添加字符。
 
可以像下面这样使用此对象:
NSString *emptyString = [[NSString alloc] init];
 
上面的代码返回一个空字符串。
 
- (id) initWithFormat: (NSString *) format, ...;
 
正如我们使用NSLog()函数和类方法stringWithFormat:接收格式化的字符串并输出格式化的结果一样,这个版本的代码初始化了一个新的字符串作为格式化操作的结果。使用此初始化方法的例子如下:
string = [[NSString alloc] initWithFormat: @"%d or %d ", 25, 624];
 
-(id) initWithContentsOfFile:(NSString *) path encoding: (NSStringEncoding) enc error: (NSEror **) error
这个方法用来打开指定路径上的文本文件,读取文件内容,并使用文件内容初始化一个字符串。读取/tmp/words.txt代码如下:
 
NSError *error = nil;
NSString *string = [[NSString alloc] initWithContentsOfFile: @"/tmp/words.txt" encoding: NSUTF8StringEncoding error: &error];
 
encoding参数将文件内容的类型告诉了API,一般来说,你应该使用NSUTF8StringEncoding,它表示文件内容是用UTF8格式进行编码的。
 
第三个参数会在初始化没有发生错误时返回nil值。如果出现了错误,你可以使用localizedDescription方法来查明情况。把它放进去的话,代码应该会如下所示:
 
NSError *error = nil;
NSStringEncoding encoding = NSUTF8StringEncoding;
NSString *string = [[NSString alloc] intiWithContentsOfFile: @"/tmp/words.txt" usedEncoding: &encoding error: &error];
if (nil != error)
{
     NSLog(@"Unable to read data from file, %@", [error localizedDescription]);
}
示例:
Tire类的初始化:
扩展Tire类以记录轮胎压力和花纹深度。
#import <Cocoa/Cocoa.h>
@interface Tire : NSObject
{
     float pressure;
     float treadDepth;
}
-(void) setPressure: (float) pressure;
-(float) pressure;
-(void) setTreadDepth: (float) treadDepth;
-(float) treadDepth;
@end // Tire
 
实现:
#import "Tire.h"
@impelementation Tire
-(id) init
{
     if (self = [super init])
     {
          pressure  = 34.0;
          treadDepth = 20.0;
     }
     return (self);
} // init
 
-(void) setPressure: (float) p
{
     pressure = p;
}
-(float) pressure
{
     return (pressure);
} // pressure
- (void) setTreadDepth: (float) td
{
     treadDepth = td;
} // setTreadDepth
 
-(float) treadDepth
{
     return (treadDepth);
} // treadDepth
 
-(NSString *) description
{
     NSString *desc;
     desc = [NSString stringWithFormat: @"Tire: Pressure: %.1f TreadDepth: %.1f, pressur, treadDepth"];
     return (desc);
} // description
@end // Tire
 
创建一个全新的tire对象:
Tire *tire = [[Tire alloc] init];
 
这个tire对象的轮胎压力将是34psi(1psi = 0.06896bar),花纹深度将是20mm.
修改了description方法,现在该方法使用NSString的类方法stringWithFormat:生成了一个包含轮胎压力和花纹深度的字符串。
 
该description方法有咩有遵守良好的内存管理规则?
有的,因为desc对象不是通过alloc,copy和new方法创建的,所以它的保留计数器的值为1,而且可以认为它是自动释放的,因此,当自动释放池销毁时,该字符串对象也将被清理。
 
更新后的main()方法:
#import "Engine.h"
#import "Car.h"
#import "Slant6.h"
#import "AllWeatherRadial.h"
 
int main(int argc, const char * argv[])
{
     @autoreleasepool
     {
          Car *car = [[Car alloc] init];
          for (int i = 0; i < 4; i++){
               Tire *tire;
               tire = [[Tire alloc] init];
               [tire setPressure: 23 + i]; [tire setTreadDepth: 33 - i];
               [car setTire: tire atIndex: i];
               [tire release];
          }
 
          Engine *engine = [[Slant6 alloc] init];
          [car setEngine: engine];
          [car print];
          [car release];
     }
     return (0);
} // main
 
清理Car类:
 
接口类:
#import <Cocoa/Cocoa.h>
@class Tire;
@class Engine;
@interface Car : NSObject
{
     NSMutableArray *tires;
     Engine *engine;
}
-(void) setEngine: (Engine *) newEngine;
-(Engine *) engine;
-(void) setTire: (Tire *) tire
atIndex: (int) index;
-(Tire *) tireAtIndex: (int) index;
-(void) print;
@end // Car
 
修改Car类的每一个方法,以使其遵循内存管理规则。
-(id) init
{
     if (self = [super init])
     {
          tires = [[NSMutableArray alloc] init];
          for (int i = 0; i < 4; i++)
          {
               [tires addObject: [NSNull null]];
          }
      }
     return (self);
} // init
 
[tires addObject: [NSNull null]];--在NSMutableArray类里面有一个简便的方法replaceObjectAtIndex:withObject:,该方法最 适合用来实现setTire:atIndex:方法。如果要使用该方法,在指定的索引位置处必须存在一个能够被替换的对象。全新的 NSMutableArray数组不包含任何内容,因此需要使用一些对象来作为占位符,而NSNull类的对象非常适合完成此项工作。
 
setEngine和engine:
-(void) setEngine: (Engine *) newEngine
{
     [newEngine retain];
     [engine release];
     engine = newEngine;
} // setEngine
 
-(Engine *) engine
{
     return (engine)
} // engine
 
tire对象的访问方法,首先是setter方法:
-(void) setTire: (Tire *) tire
atIndex: (int) index
{
     [tires replaceObjectAtIndex: index withObject: tire];
} // setTire:atIndex
 
setTire: 方法使用replaceObjectAtIndex:withObject:从数组集合中删除现有对象并用新对象代替。因为NSMutableArray 数组会自动保留新的tire对象并释放索引位置上的对象(无论该对象是NSNull占位符还是tire对象),所以对于tire对象不需要执行其他内存管 理。当NSMutableArray数组被销毁时,它将释放数组中的所有对象,因此tire对象会被清理。
 
getter方法
-(Tire *) tireAtIndex: (int) index
{
     Tire *tire;
     tire = [tires objectAtIndex: index];
     return (tire);
} // tireAtIndex
 
 
确保car能够清理其保留的对象,dealloc方法就可以完成此类工作:
-(void) dealloc
{
     [tires release];
     [engine release];
     [super dealloc];
} // dealloc
 
 
print方法:
-(void) print
{
     for (int i = 0; i < 4; i++)
     {
          NSLog(@"%@", [self tireAtIndex: i]);
     }
     NSLog(@"%@", engine);
} // print
 
该循环使用tireAtIndex: 方法间接取值而不是直接从数组中取值。
 
Car类的内存清理(垃圾回收方式和ARC方式)
在支持垃圾回收的Objective-C中,setEngine方法变得更加简单:
-(void) setEngine: (Engine *) newEngine
{
     engine = newEngine;
} // setEngine
 
我 们修改了engine对象的实例变量。当Cocoa的垃圾回收机制运行时,它知道没有其他实例变量指向原来的engine对象,因此,垃圾回收销毁了原来 的engine对象。另一方面,因为有一个实例变量指向了newEngine对象,所以newEngine对象不会被回收。垃圾回收器知道有变量正在使用 newEngine对象。
 
dealloc方法完全消失,如果在销毁对象时执行某些操作,则需要重写-finalize方法,当对象最终被回收时该方法会被调用。
 
ARC版本与垃圾回收版本类似,唯一要加的就是@autoreleasepool,来告诉编译器我们想要它来处理保留和释放事件。
 
构造便利初始化函数
 
新的Tire类代码:
@interface Tire: NSObject
{
     float pressure;
     float treadDepth;
}
 
-(id) initWitnPressure: (float) pressure
treadDepth: (float) treadDepth;
 
-(void) setPressure: (float) pressure;
-(float) pressure;
-(void) setTreadDepth: (float) treadDepth;
-(float) treadDepth;
@end // Tire
 
毫无疑问,该方法的实现非常简单:
-(id) initWithPressure: (float) p treadDepth: (float) td
{
     if (self = [super init]){
          pressure = p;
          treadDepth = td;
     }
     return (self);
} // initWithPressure
 
单步操作中完成tire对象的分配和初始化了。
Tire *tire;
tire = [[Tire alloc]]
initWithPressure: 23+i
treadDepth:33 - i;
 
指定初始化函数
并不是所有的初始化方法都能够正常运行。当在类中增加便利初始化函数时,会出现一些小问题。
 
@interface Tire : NSObject
{
     float pressure;
     float treadDepth;
}
-(id) initWithPressure: (float) pressure;
-(id) initWithTreadDepth: (float) treadDepth;
-(id) initWithPressure: (float) pressure treadDepth: (float) treadDepth;
-(void) setPressure: (float) pressure;
-(float) pressure;
-(void) setTreadDepth: (float)treadDepth;
-(float) treadDepth;
@end // Tire
 
initWithPressure:和initWithTreadDepth:这两个新的初始化函数适用于那些知道轮胎需要特定轮胎压力或花纹深度而不关心其他属性(并且乐意接受默认值)的人。第一次初始化方法如下:
 
-(id) initWithPressure: (float) p
{
     if (self = [super init]){
          pressure = p;
          treadDepth = 20.0;
     }
     return (self);
} // initWithPressure
 
-(id) initWithTreadDepth: (float) td
{
     if (self = [super init]){
          pressure = 34.0;
          treadDepth = td;
     }
     return (self);
 
} // initWithTreadDepth
 
现 在有了4个初始化方法:init、initWithPressure:、initWithTreadDepth:和 initWithPressure:treadDepth:,每个都知道默认的轮胎压力值(34)或花纹深度值(20)。这些代码正确方法可以正确运行, 但是开始子类化Tire类时,就有问题了。
 
子类化问题:
给AllWeatherRadial类增加两个实例变量:rainHandling和snowHandling.都是浮点值。
新的接口:
@interface AllWeatherRadial: Tire
{
     float rainHandling;
     float snowHangling;
}
-(void) setRainHandling: (float) rainHandling;
-(float) rainHandling;
-(void) setSnowHangling: (float) snowHandling;
-(float) snowHangling;
@end // AllWeatherRadial
访问的方法:
 
-(void) setRainHandling: (float) rh
{
     rainHandling = rh;
} // setRainHandling
 
-(float) rainHandling
{
     return (rainHandling);
} // rainHandling
 
-(void) setSnowHandling: (float) sh
{
     snowHandling = sh;
} // setSnowHandling
 
-(float) snowHandling
{
     return (snowHandling);
} // snowHandling
 
description方法:
-(NSString *) description
{
     NSString *desc;
     desc = [[NSString alloc] initWithFormat: @"AllWeatherRadial: %.1f / %.1f / %.1f / %.1f", 
               [self pressure], [self treadDepth], [self rainHandling], [self snowHandling]];
     return (desc);
} // description
 
main函数的for循环:
for (int i = 0; i < 4; i++)
{
     AllWeatherRadial *tire; 
     tire = [[AllWeatherRadial alloc] init];
     [car setTire: tire atIndex: i];
     [tire release];
}
运行结果出了问题:
 
AllWeatherRadial 对象的新变量未被设置为合理的默认值。因为在init方法中赋值,我们必须重写init方法。当在Tire类中还有initWithPressure:、 initWithTreadDepth:和initWithPressure:treadDepth:三个初始化方法,难道需要重写这三个方法,就算重写 了,如果以后还要加入新的方法,所以这样的程序设计的方式时候问题的。
 
为解决这个问题Cocoa提出了指定初始化函数(designated initializer)这一概念,即类中的某个初始化方法被指派为指定初始化函数,该类的所有初始化方法都使用指定初始化函数执行初始化操作,而子类使用其超类的指定初始化函数进行超类的初始化。通常,接受参数最多的初始化方法是最终的指定初始化函数。如果你使用了其他人的缩写的代码,则一定要检查文档弄清楚哪个方法是指定初始化函数。
 
Tire类的初始化函数改进版:
把initWithPressure:treadDepth:指派为指定初始化函数。所有其他的初始化函数应该按照initWithPressure:treadDepth:的形式实现。
可能如下:
-(id) init
{
     if (self = [self initWithPressure: 34 treadDepth: 20])
     {}
     return (self);
} // init
 
-(id) initWithPressure: (float) p
{
     if (self = [self initWithPressure: p treadDepth: 20.0]){}
     return (self);
} // initWithPressure
 
-(id) initWithTreadDepth: (float) td
{
     if (self = [self initWithPressure: 34.0 treadDepth: td]){}
     return (self);
} // initWithTreadDepth
 
 
现在应该向AllWeatherRadial类中添加指定初始化函数了:
-(id) initWithPressure: (float) p treadDepth: (float) td
{
     if (self = [super initWithPressure: p treadDepth: td])
     {
          rainHandling = 23.7;
          snowHandling = 42.5;
     }
     return (self);
} // initWithPressure:treadDepth
 
 
属性
 
苹果公司在Objective-C 2.0中引入了属性(property),它组合了新的预编译指令和新的属性访问器语法。
请记住:Objective-C 2.0的特性只适用于Mac OS X 10.5(Leopard)以上版本。
 
使用属性值:简化AllWeatherRadial类的接口代码:
旧的:
#import <Foundation/Foundation.h>
#import "Tire.h"
@interface AllWeatherRadial : Tire
{
     float rainHandling;
     float snowHandling;
}
-(void) setRainHandling: (float) rainHandling;
-(float) rainHandling;
-(void) setSnowHandling: (float) snowHandling;
-(float) snowHandling;
@end // AllWeatherRadial
具有属性风格的:
#import <Foundation/Foundation.h>
#import "Tire.h"
@interface AllWeatherRadial : Tire
{
     float rainHandling;
     float snowHandling;
}
@property float rainHandling;
@property float snowHandling;
@end // AllWeaherRadial
我们引入了两个以@为前缀的关键字。@property是一种新的编译器功能,它意味着声明了一个新对象的属性。
 
@property预编译指令的作用是自动声明属性的setter和getter方法。
@property float rainHandling; 语句表明AllWeatherRadial类的对象具有float类型的属性,其名称为rainHandling。可以通过调用 
-setRainHandling:来设置属性,通过调用-rainHandling来访问属性。
 
简化AllWeatherRadial类的实现代码:
旧的:
#import "AllWeatherRadial.h"
@implementation AllWeatherRadial
-(id) initWithPressure: (float) p treadDepth: (float) td
{
     if (self = [super initWithPressure: p treadDepth: td]){
          rainHandling = 23.7;
          snowHandling = 42.5;
     }
     return (self);
} // initWithPressure:treadDepth
 
-(void) setRainHandling: (float) rh
{
     rainHandling = rh;
} // setRainHandling
 
-(float) rainHandling
{
     return (rainHandling);
} // rainHandling
 
-(void) setSnowHandling: (float) sh
{
     snowHandling = sh
} // setSnowHandling
 
-(float) snowHandling
{
     return (snowHandling);
}// snowHandling
 
-(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
 
删除全部的setter和getter方法,用两行简单的代码来代替:
#import "AllWeatherRadial.h"
@implementation AllWeatherRadial
@synthesize rainHandling;
@synthesize snowHandling;
-(id) initWithPressure: (float) p treadDepth : td
{
    if (self = [super initWithPressure: p treadDepth: td])
     {
          rainHandling = 23.7;
          snowHandling = 42.5;
     }
     return (self);
} // initWithProssure: treadDepth
 
-(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
 
@synthesize(在 Xcode4.5以后的版本中,可以不必使用@synthesize )也是一种新的编译器功能,它表示“创建了该属性的访问代码”。当遇到 @synthesize rainHandling;这行代码时,编译器将添加实现-setRainHandling: 和 -rainHandling方法的预编译代码。
 
所有的属性都是基于变量的。所以你在合成(synthesize)getter和setter方法的时候,编译器会自动创建与属性名称相同的实例变量。
如果你没有声明头文件中的rainHandling和snowHandling两个实例变量,编译器也会声明的。有两个地方可以用来添加实例变量的声明:头文件和实现文件。
声明在这两个地方的不同处:如果有一个子类,并且想要从子类直接通过属性来访问变量,那么变量就必须放在头文件中。
如果变量只属于当前类,则可以把它们放在.m文件里(并且要删除interface代码中的声明语句)。
 
修改后的代码:
@interface AllWeatherRadial : Tire
@property float rainHandling;
@property float snowHandling;
@end // AllWeatherRadial
 
@implementation AllWeatherRadial
{
     float rainHandling;
     float snowHandling;
}
 
@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);
} // initWithPressure:treadDepth
 
-(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
 
记住:如果没有指定实例变量,编译器会自动帮我们创建,所以我们可以删除以下代码,这不会有任何影响:
{
     float rainHandling;
     float snowHandling;
}
 
点表达式的妙用:
 
[tire setRainHandling: 20+i];
替换为
tire.rainHandling = 20 + i;
结果一样。
 
点表达式(.)出现在了等号(=)的左边,该变量名称的setter方法(-setRainHandling:)将被调用。如果点表达式出现在了对象变量的右边,则该变量名称的getter方法(-rainHandling)将被调用。
 
 
如果在访问属性时遇到了奇怪的错误信息,提示访问的对象不是struct类型,请检查当前的类是否已经包含了所需的所有必备的头文件。
 
 
给car类添加一种新的特性。
Car.h,新添加一个name实例变量
@class Tire;
@calss Engine;
@interface Car : NSObject
{
     NSString *name;
     NSMutableArray *tires;
     Engine *engine;
}
 
-(void) setName: (NSString *) newEngine;
-(NSString *) name;
-(void) setEngine: (Engine *) newEngine;
-(Engine *) engine;
-(void) setTire: (Tire *) tire atIndex: (int) index;
-(Tire *) tireAtIndex : (int) index;
-(void) print;
@end // Car
 
现在添加访问方法的实现(请注意我们是在复制name变量),同时为car对象选择默认的名称。
 
#import "Car.h"
@implementation Car
-(id) init
{
     if (self = [super init]){
          name = [[NSString alloc] initWithProssure: @"Car"];
          tires = [[NSMutableArray alloc] init];
          int i;
          for (i = 0; i < 4; i++){
               [tires addObject: [NSNull null]];
          }
     }
     return (self);
} // init
 
-(void) dealloc
{
     [name release];
     [tires release];
     [engine release]; 
     [super release];
} // dealloc
 
-(void) setName : (NSString *) newName
{
     [name release];
     name = [newName copy];
} // setName
 
-(NSString *) name
{
     return (name);
} // name
 
-(Engine *) engine
{
     return (engine);
} // engine
 
-(void) setEngine: (Engine *) newEngine
{
     [newEngine retain];
     [engine release];
     engine = newEngine;
} // setEngine
 
-(void) setTire: (Tire *) tire atIndex : index
{
     [tires replaceObjectAtIndex: index withObject: tire];
} // setTire:atIndex:
 
-(Tire *) tireAtIndex: (int) index
{
     Tire *tire;
     tire = [tires objectAtIndex: index];
     return (tire);
} // tireAtIndex:
 
-(void) print
{
     NSLog(@"%@ has: ", name );
     for (int i = 0; i < 4; i++){
          NSLog(@"%@", [self tireAtIndex: i]);
     }
     NSLog(@"%@", engine);
} // print
 
@end // Car
在main()函数中设置name对象的值:
Car *car = [[Car alloc] init];
[car setName: @"Herbie"];
 
向Car类添加属性:
新的Car.h
@class Tire;
@class Engine;
@interface Car : NSObject
{
     NSString *name;
     NSMutableArray *tires;
     Engine *engine;
}
@property (copy) NSString *name;
@property (retain) Engine *engine;
-(void) setTire: (Tire *) tire atIndex: (int) index;
-(Tire *) tireAtIndex: (int) index;
-(void) print;
@end // Car
 
访问方法的声明已经被@property什么取代。还可以声明具有其他特性的@property语句,表达希望属性具有怎样的行为。 如name属性使用的是copy特性,所以编译器和类的使用者会知道name属性将被复制。
 
Car.m有两个重大的变化,一个是name和engine的访问方法删除了,另一个是添加了两条@synthesize指令:
 
@implementation Car
 
@synthesize name;
@synthesize engine;
 
最后,main()函数使用了点表达式来给对象赋值。
 
Car *car = [[Car alloc] init];
car.name = @"Herbie";
...
car.engine = [[Slant6 alloc] init];
 
名称的使用
假设我们想要在Car类中使用其他名称(比如appellation)来调用实例变量,只需在Car.h文件中修改该实例变量的名称:
@interface Car : NSObject
{
     NSString *appellation;
     NSMutableArray *tires;
     Engine *engine;
}
 
@property (copy) NSString *name;
@property (retain) Engine *engine;
 
然后再修改@synthesize指令:
@synthesize name = appellation;
 
编 译器仍将创建-setName:和-name方法,但在其实现代码中用的却是appellation实例变量。这样做的话,编译的时候将会遇到一些错误, 我们直接访问的实例变量已经被修改了,我们可以选择用搜索并替换name的方式来解决,也可以将实例变量的直接调用改成访问方法。比如在init方法中 把:
name = @"Car";
改成:
self.name = @"Car";
 
self.name的作用是消除歧义, 是编译器知道我们会用访问方式来读取变量。如果只使用普通的name,编译器会误以为我们要修改这个名name的实例变量。如果要使用访问方法来进行赋值,可以写成[self setName: @"Car"]。
 
请记住,点表示法只是调用相同方法的便捷方式,所以self.name = @"Car"只不过是实现同样内容的另一种写法。
 
最后,我们必须修改NSLog()函数的以下这句:
NSLog(@"%@ has: ", self.name);
 
 
AllWeatherRadial.m文件中的description方法改为:
-(NSString *) description
{
     NSString *desc;
     desc = [[NSString alloc] initWithFormat: @"AllWeatherRadial: %.1f / %.1f / %.1f / %.1f", 
               self.pressure, self.treadDepth, self.rainHandling, self.snowHangling];
     return (desc);
} // description
 
接下来将修改Tire类的pressure和treadDepth属性,还要删除getter和setter方法。被简化的Tire类并不会丢失任何功能。
 
@interface Tire: NSObject
 
@property float pressure;
@property float treadDepth;
 
-(id) initWithPressure: (float) pressure;
-(id) initWithTreadDepth: (float) treadDepth;
-(id) initWithPressure: (float) treadDepth;
 
@end // Tire
 
还删除了实现文件中的setter和getter方法。
 
 
只读属性
 
可以使某个对象具有只读属性。
 
默认情况下,属性是可变的(你可以读取也可以修改).也可以使用属性的readwrite特性,
@property (readwrite, copy) NSString *name;,
不过一般都会尽量杜绝和消除冗余的重复代码而不这样做。
 
只读属性的代码:
@interface Me : NSObject
{
     float shoeSize;
     NSString *licenseNumber;
}
 
@property (readonly) float shoeSize;
@property (readonly) NSString *licenseNumber;
@end 
 
当编译器知道这个@property属性是只读的,它将只生成一个getter方法而不会生成setter方法。
 
如果不想要变量、getter和setter方法的话应该怎么做:这种情况可以使用关键字@dynamic来告诉编译器不要生成任何代码或创建相应的实例变量。
 
@property (readonly) float bodyMassIndex;
 
@dynamic bodyMassIndex;
-(float) bodyMassIndex
{
     /// comoute and return bodyMassIndex
}
如果你声明了dynamic属性,并且企图调用不存在的getter或setter方法,将会得到一个报错。
 
使用getter= 和 setter= 特性可以自定义想要的方法名称。这样做需要注意会破坏键/值规则,因此除非必须使用这些特性的原因,否则请尽量避免使用。
 
@property (getter = isHidden) BOOL hidden;
 
告诉编译器生成名为isHidden的getter方法。并生成名为默认setHidden:的setter方法。
 
特性不是万能的,没有转换tire对象的访问方法以支持属性:
-(void) setTire: (Tire *) tire atIndex: (int) index;
-(Tire *) tireAtIndex: (int) index;
 

属性只支持替代-setBlah和-blah方法,但是不支持那些需要接受额外参数的方法。例如car对象中tire对象的代码

posted @ 2015-11-30 16:29  三恒一书  阅读(325)  评论(0编辑  收藏  举报