Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a pattern to override a property?

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?

like image 310
Ding Curie Avatar asked May 29 '12 06:05

Ding Curie


People also ask

Can inherited properties be overridden?

Overriding Properties and Methods in Derived Classes By default, a derived class inherits properties and methods from its base class. If an inherited property or method has to behave differently in the derived class it can be overridden. That is, you can define a new implementation of the method in the derived class.

Can I override a property in Java?

You cannot "override" fields, because only methods can have overrides (and they are not allowed to be static or private for that).

How do you override a base class property?

you can override properties just like methods. That is, if the base method is virtual or abstract. You should use "new" instead of "override". "override" is used for "virtual" properties.

What is override as used in inheritance?

A derived class has the ability to redefine, or override, an inherited method, replacing the inherited method by one that is specifically designed for the derived class.


1 Answers

My take on this would be slightly different. In the case of the @interface of an Objective-C class, you are declaring the API that class uses with all classes that communicate with it. By replacing the NSString* copy property with an NSMutableString* strong property, you are creating a situation where unexpected side-effects are likely to occur.

In particular, an NSString* copy property is expected to return an immutable object, which would be safe for using in many situations that an NSMutableString* object would not be (keys in dictionaries, element names in an NSXMLElement). As such, you really don't want to replace these in this fashion.

If you need an underlying NSMutableString, I would suggest the following:

  • Add an NSMutableString* property in addition to the string property, and name it -mutableString
  • Override the -setString: method to create an NSMutableString and store it
  • Override the -string method to return an immutable copy of your mutable string
  • Carefully evaluate whether you can replace the internal ivar with an NSMutableString or not. This might be a problem if you don't have access to the original class and you aren't certain whether assumptions are made about the mutability of the string inside of the class

If you do this, you will maintain the current interface without disrupting existing users of the class, while extending the behavior to accommodate your new paradigm.

In the case of changing between a mutable object and an immutable one, you really need to be careful that you don't break the API contract for the object.

like image 178
gaige Avatar answered Oct 04 '22 06:10

gaige