您可以拥有一个公共只读属性,并使用私有读写属性为您的类中的属性提供设置器(如果您确实需要)。但是,您应该考虑是否有必要。
作为示例,请考虑以下不可变 Person 类的声明和定义:
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject {
@private
NSString *name_;
NSDate *dateOfBirth_;
}
@property (readonly, copy) NSString *name;
@property (readonly, copy) NSDate *dateOfBirth;
/*! Initializes a Person with copies of the given name and date of birth. */
- (id)initWithName:(NSString *)name dateOfBirth:(NSDate *)dateOfBirth;
@end
// Person.m
#import "Person.h"
@implementation Person
@synthesize name = name_;
@synthesize dateOfBirth = dateOfBirth_;
- (id)initWithName:(NSString *)name dateOfBirth:(NSDate *)dateOfBirth {
self = [super init];
if (self) {
name_ = [name copy];
dateOfBirth_ = [dateOfBirth copy];
}
return self;
}
- (void)dealloc {
[name_ release];
[dateOfBirth_ release];
[super dealloc];
}
@end
首先,请注意我did not声明一个类扩展Person.m
重新声明了name
and dateOfBirth
属性为readwrite
。这是因为类的目的是不可变的;如果实例变量只在初始化时设置,则不需要设置器。
另请注意,我声明的实例变量的名称与属性的名称不同。这清楚地表明了属性之间的区别编程接口类和实例变量作为的实施细节班上。我见过太多的开发人员(尤其是那些刚接触 Mac OS X 和 iOS 的开发人员,包括许多来自 C# 的开发人员)将属性与可用于实现它们的实例变量混为一谈。
第三件事要注意的是我将这两个属性声明为copy
即使它们是只读的。有两个原因。首先,虽然此类的直接实例是不可变的,但没有什么可以阻止创建 MutablePerson 子类。事实上,这甚至可能是可取的!所以copy
明确指定超类的期望是什么 - 超类的值name
and dateOfBirth
属性本身不会改变。这也暗示着-initWithName:dateOfBirth:
可能也是复制品;它的文档注释应该清楚地说明这一点。其次,NSString和NSDate都是值类;不可变子类的副本应该很便宜,并且您不想保留将从您自己的类下更改的可变子类的实例。 (现在实际上没有 NSDate 的任何可变子类,但这并不意味着有人无法创建自己的......)
最后,不必担心指定的初始值设定项是否冗长。如果对象的实例除非处于某种特定状态,否则无效,那么您指定的初始值设定项需要将其置于该状态,并且需要采用适当的参数来执行此操作。
还有一件事:如果您要创建这样的不可变值类,您可能还应该实现自己的-isEqual:
and -hash
快速比较的方法,并且可能也符合 NSCopying 。例如:
@interface Person (ImmutableValueClass) <NSCopying>
@end
@implementation Person (ImmutableValueClass)
- (NSUInteger)hash {
return [name_ hash];
}
- (BOOL)isEqual:(id)other {
Person *otherPerson = other;
// Using [super isEqual:] to allow easier reparenting
// -[NSObject isEqual:] is documented as just doing pointer comparison
return ([super isEqual:otherPerson]
|| ([object isKindOfClass:[self class]]
&& [self.name isEqual:otherPerson.name]
&& [self.dateOfBirth isEqual:otherPerson.dateOfBirth]));
}
- (id)copyWithZone:(NSZone *)zone {
return [self retain];
}
@end
我在它自己的类别中声明了它,以便不重复我之前作为示例显示的所有代码,但在实际代码中,我可能会将所有这些放在 main 中@interface
and @implementation
。请注意,我没有重新声明-hash
and -isEqual:
,我只定义了它们,因为它们已经由 NSObject 声明了。因为这是一个不可变的值类,所以我可以实现-copyWithZone:
纯粹通过保留self
,我不需要制作该对象的物理副本,因为它的行为应该完全相同。
但是,如果您使用的是 Core Data,请不要这样做; Core Data 实现了您独有的对象,因此您must not有你自己的-hash
or -isEqual:
执行。为了更好的衡量,你也不应该真正遵守 Core Data NSManagedObject 子类中的 NSCopying ; “复制”属于 Core Data 对象图一部分的对象意味着什么,需要仔细思考,并且通常更多是控制器级别的行为。