6

The Objective-C runtime keeps a list of declared properties as meta-data with a Class object. The meta-data includes property name, type, and attributes. The runtime library also provides a couple of functions to retrieve these information. It means a declared property is more than a pair of accessor methods (getter/setter). My first question is: Why we (or the runtime) need the meta-data?

As is well known, a declared property cannot be overridden in subclasses (except readwrite vs. readonly). But I have a scenario that guarantees that needs:

@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

Of course, the compiler won't let the above code pass through. My solution is to substitute the declared property with a pair of accessor methods (with the readonly case, just the 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

Property string needs to be mutable because it is modified incrementally and potentially frequently. I know the constraint that methods with the same selector should share the same return and parameter types. But I think the above solution is appropriate both semantically and technically. For the semantic aspect, a mutable object is a immutable object. For the technical aspect, the compiler encodes all objects as id's. My second question is: Does the above solution make sense? Or it's just odd?

I can also take a hybrid approach, as follows:

@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

However, when I access the property using the dot syntax like myMutableObject.string, the compiler warns that the return type of the accessor method does not match the type of the declared property. It's OK to use the message form as [myMutableObject string]. That suggests another aspect where a declared property is more than a pair of accessor methods, that is, more static type checking, although it is undesirable here. My third question is: Is it common to use getter/setter pair instead of declared property when it is intended to be overridden in subclasses?

4

1 回答 1

3

我对此的看法会略有不同。在@interfaceObjective-C 类的情况下,您正在声明该类与所有与之通信的类一起使用的 API。通过将NSString*copy 属性替换为NSMutableString*strong 属性,您正在创造一种可能会发生意外副作用的情况。

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

如果您需要底层证券NSMutableString,我会建议以下内容:

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

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

在可变对象和不可变对象之间进行更改的情况下,您确实需要小心不要破坏对象的 API 契约。

于 2012-05-29T11:50:26.640 回答