我们在设计类的时候,运用属性来封装数据。并且使用属性时候,可以将属性直接设置为“只读”(read-only)。默认情况下是“可读也可写”的。
不过,一般情况下我们要建模的数据未必需要改变。比方说,某数据所表示的对象源自一项只读的网络服务(web service),里面可能包含一系列需要显示在地图上的相关点,像这种对象就没必要改变其内容。即使修改了,新数据也不会推送回服务器.
而且如果把可变对象(mutable object)放入collection
之后又修改其内容,那么很容易就会破坏set
的内部数据结构,使其失去固有的语义。
我们来举例:
为了将EOCPointOflnterest
做成不可变的类,需要把所有属性都声明为readonly
:
#import <Foundation/Foundation.h>
@interface EOCPointOfInterest : NSObject
@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, copy, readonly) NSString* title;
@property (nonatomic, assign, readonly) float latitude;
@property (nonatomic, assign, readonly) float longitude;
-(id) initWithldentifier: (NSString*) identifier
title:(NSString*)title
latitude: (float) latitude
longitude: (float) longitude;
@end
这样后如果有人想要改变属性值,那么编译时就会报错。对象中的属性值可以读出,但是无法写入,这就能保证EOCPointOfluterest
中的各个数据之间总是相互协调的。
但是我们有时想要修改封装在对象内部的数据,不想令这些数据为外人所改动。这种情况下,通常做法是在对象内部将readonly
属性重新声明为readwrite
。当然,如果该属性是nonatomic 的,那么这样做可能会产生“竞争条件”(racecondition)。在对象内部写人某属性时,对象外的 观察者也许正读取该属性。若想避免此问题,我们可以在必要时通过“派发队列"(dispatchqueue)等手段,将(包括对象内部的)所有数据存取操作都设为同步操作。将属性在对象内部重新声明为readwrite
这一操作可于“class-continuation分类”
中完成,在公共接口中声明的属性可于此处重新声明,属性的其他特质必须保持不变,而readonly
可扩展为readwrite
。
其 “class-continuation分类”
可以这样写:
// .m文件中
#import "EOCPointOfInterest.h"
@interface EOCPointOfInterest : NSObject
@property (nonatomic, copy, readwrite) NSString *identifier;
@property (nonatomic, copy, readwrite) NSString* title;
@property (nonatomic, assign, readwrite) float latitude;
@property (nonatomic, assign, readwrite) float longitude;
@implementation EOCPointOfInterest
/* ... */
@end
现在,只能于EOCPoimOflnterest
实现代码内部设置这些属性值了。但是我们其实可以同构KVC键值编码来设置这些属性值。不过,这样显然违背了我们的本意,绕过了提供的API。不推荐这种做法。
我们定义类公共的API时,要注意一件事情:对象里表示各种collection的那些属性究竞应该设成可变的,还是不可变的。例如,我们用某个类来表示个人信息,该类里还存放了一些引用,指向此人的诸位朋友。你可能想把这个人的全部朋友都放在一个“列表"(list)里,并将其做成属性。假如开发者可以添加或删除此人的朋友,那么这个属性就需要用可变的set
来实现。在这种情况下,通常应该提供一个readonly
属性供外界使用,该属性将返回不可变的set
, 而此set
则是内部那个可变set
的一份拷贝。比方说,下面这段代码就能够实现出这样一个类:
// EOCPerson.h
#import <Foundation/Foundation•h>
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSSet *friends;
-(id)initWithFirstName:(NSString*)firstName
andLastName:(NSString*)lastName;
-(void)addFriend:(EOCPerson*)person;
-(void)removeFriend:(EOCPerson*)person;
@end
// EOCPerson.m #import "EOCPerson•!!
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation EOCPerson {
NSMutableSet *_internalFriends;
}
-(NSSet*)friends {
return [_internalFriends copy];
}
-(void)addFriend:(EOCPerson*)person {
[_internalFriends addObject:person];
}
-(void)removeFriend:(EOCPerson*)person {
[_internalFriends removeObjectrperson];
}
-(id)initWithFirstName: (NSString*)firstName andLastName:(NSString*)lastName {
if ((self = [super init】)){
_firstName = firstName;
_lastName = lastName;
_internalFriends = [NSMutableSet new];
}
return self;
}
@end
要点
- 尽量创建不可变的对象。
- 若某属性仅可于对象内部修改,则在
“class-continuation分类”
中将其由readonly
属性扩展为readwrite
属性 - 不要把可变的
collection
作为属性公开,而应提供相关方法,以此修改对象中的可变collection
。