是否有覆盖属性的模式?

时间:2012-05-29 06:45:03

标签: objective-c accessor mutability declared-property

Objective-C runtime将声明的属性列表保存为带有Class对象的元数据。元数据包括属性名称,类型和属性。运行时库还提供了一些函数来检索这些信息。这意味着声明的属性不仅仅是一对存取方法(getter / setter)。我的第一个问题是:为什么我们(或运行时)需要元数据?

众所周知,在子类中不能覆盖声明的属性(readwrite与readonly除外)。但我有一个场景可以保证需要:

@interface MyClass : MySuperClass <NSCopying, NSMutableCopying>

@property (nonatomic, copy, readonly) NSString *string;

- (id)initWithString:(NSString *)aString;

@end


@interface MyMutableClass : MyClass

@property (nonatomic, strong, readwrite) NSMutableString *string;

- (id)initWithString:(NSString *)aString;

@end

当然,编译器不会让上面的代码通过。我的解决方案是使用一对访问器方法替换声明的属性(使用readonly case,只是getter):

@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
    NSString *_string;
}

- (id)initWithString:(NSString *)aString;

- (NSString *)string;

@end


@implementation MyClass

- (id)initWithString:(NSString *)aString {
    self = [super init...];
    if (self) {
        _string = [aString copy];
    }
    return self;
}

- (NSString *)string {
    return _string;
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return [[MyMutableClass alloc] initWithString:self.string];
}

@end


@interface MyMutableClass : MyClass

- (id)initWithString:(NSString *)aString;

- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;

- (void)didMutateString;

@end


@implementation MyMutableClass

- (id)initWithString:(NSString *)aString {
    self = [super init...];
    if (self) {
        _string = [aString mutableCopy];
    }
    return self;
}

- (NSMutableString *)string {
    return (NSMutableString *)_string;
}

- (void)setString:(NSMutableString *)aMutableString {
    _string = aMutableString;

    // Inform other parts that `string` has been changed (as a whole).
    // ...
}

- (void)didMutateString {
    // The content of `string` has been changed through the interface of
    // NSMutableString, beneath the accessor method.
    // ...
}

- (id)copyWithZone:(NSZone *)zone {
    return [[MyClass alloc] initWithString:self.string];
}

@end

属性string需要是可变的,因为它是逐步修改的,并且可能经常修改。我知道具有相同选择器的方法应该共享相同的返回和参数类型的约束。但我认为上述解决方案在语义和技术上都是合适的。对于语义方面,可变对象不可变对象。对于技术方面,编译器将所有对象编码为 id 。我的第二个问题是:上述解决方案是否有意义?或者它只是奇怪的?

我也可以采用混合方式,如下:

@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
    NSString *_string;
}

@property (nonatomic, copy, readonly) NSString *string;

- (id)initWithString:(NSString *)aString;

@end


@interface MyMutableClass: MyClass

- (id)initWithString:(NSString *)aString;

- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;

- (void)didMutateString;

@end

但是,当我使用像myMutableObject.string这样的点语法访问属性时,编译器会警告访问器方法的返回类型与声明的属性的类型不匹配。可以将消息表单用作[myMutableObject string]。这表明声明属性不仅仅是一对存取方法的另一个方面,即更多静态类型检查,尽管这里不需要。我的第三个问题是:当要在子类中重写时,使用getter / setter对而不是声明的属性是否常见?

1 个答案:

答案 0 :(得分:3)

我对此的看法会略有不同。对于Objective-C类的@interface,您声明该类使用的API与所有与之通信的类。通过将NSString*复制属性替换为NSMutableString*强属性,您可能会出现可能发生意外副作用的情况。

特别是,NSString*复制属性应该返回一个不可变对象,在NSMutableString*对象不在的许多情况下使用它是安全的(字典中的键,元素名称在NSXMLElement)。因此,你真的不想以这种方式取代它们。

如果您需要基础NSMutableString,我建议如下:

  • 除了字符串属性外,还添加NSMutableString*属性,并将其命名为-mutableString
  • 覆盖-setString:方法以创建NSMutableString并存储
  • 重写-string方法以返回可变字符串的不可变副本
  • 仔细评估是否可以用NSMutableString替换内部ivar。如果您无法访问原始类并且您不确定是否对类内部字符串的可变性进行了假设,那么这可能是一个问题

如果这样做,您将维护当前界面,而不会中断该类的现有用户,同时扩展行为以适应您的新范例。

如果在可变对象和不可变对象之间进行更改,则需要注意不要破坏对象的API协定。