您的位置:首页 > 移动开发 > Objective-C

《Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法》 笔记3 为什么要用属性

2016-04-15 14:13 821 查看
在描述个人信息的类中,也许会存放人名、生日、地址等内容。可以在类接口的public区段中声明一些实例变量:

@interface EOCPerson : NSObject {
@public
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end


原来编过Java或C++程序的人应该比较熟悉这种写法,在这些语言中,可以定义实例变量的作用域。然而编写Objective-C代码时却很少这么做。这种写法的问题是:对象布局在编译期(compile time)就已经固定了。只要碰到访问_firstName变量的代码,编译器就把其替换为“偏移量”(offset),这个偏移量是“硬编码”(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。这样做目前来看没问题,但是如果又加了一个实例变量,那就麻烦了。比如说,假设在_firstName之前又多了一个实例变量:

@interface EOCPerson : NSObject {
@public
NSDate *_dateOfBirth;
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end


原来表示_firstName的偏移量现在却指向_dateOfBirth了。把偏移量硬编码于其中的那些代码都会读取到错误的值。图2-1演示了此情况,请对比在类中加入_dateOfBirth这一实例变量之前与之后的内存布局,其中假设指针为4个字节。



如果代码使用了编译期计算出来的偏移量,那么在修改类定义之后必须重新编译,否则就会出错。例如,某个代码库中的代码使用了一份旧的类定义。如果和其相链接的代码使用了新的类定义,那么运行时就会出现不兼容现象(incompatibility)。各种编程语言都有应对此问题的办法。Objective-C的做法是,把实例变量当做一种存储偏移量所用的“特殊变量”(special variable),交由“类对象”(class object)保管(第14条详述了类对象)。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。甚至可以在运行期向类中新增实例变量,这就是稳固的“应用程序二进制接口”(Application
Binary Interface,ABI)。ABI定义了许多内容,其中一项就是生成代码时所应遵循的规范。有了这种“稳固的”(nonfragile)的ABI,我们就可以在“class-continuation分类”或实现文件中定义实例变量了。所以说,不一定要在接口中把全部实例变量都声明好,可以将某些变量从接口的public区段里移走,以便保护与类实现有关的内部信息。

这个问题还有一种解决办法,就是尽量不要直接访问实例变量,而应该通过存取方法来做。虽说属性最终还是得通过实例变量来实现,但它却提供了一种简洁的抽象机制。你可以自己编写存取方法,然而在正规的Objective-C编码风格中,存取方法有着严格的命名规范。正因为有了这种严格的命名规范,所以Objective-C这门语言才能根据名称自动创建出存取方法。这时@property语法就派上用场了。

在对象接口的定义中,可以使用属性,这是一种标准的写法,能够访问封装在对象里的数据。因此,也可以把属性当做一种简称,其意思是说:编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。例如下面这个类:

@interface EOCPerson : NSObject
@property  NSString *firstName;
@property NSString *lastName;
@end


对于该类的使用者来说,上述代码写出来的类与下面这种写法等效:

@interface EOCPerson : NSObject
- (NSString*)firstName;
- (void)setFirstName:(NSString*)firstName;
- (NSString*)lastName;
- (void)setLastName:(NSString*)lastName;
@end


然而属性还有更多优势。如果使用了属性的话,那么编译器就会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为_firstName与_lastName。也可以在类的实现代码里通过@synthesize语法来指定实例变量的名字:

@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: