Overriding @property declarations in Objective-C
Asked Answered
R

2

28

I often find that I know that a certain property of a base class will always be a certain type in a subclass. For instance, in the example below, property obj will always be an NSString object in Derived. However, I need this property to be the more generic id type in class Base.

@interface Base
@property (strong, nonatomic) id obj;
@end

@implementation Base
//@synthesize obj = obj_;
@dynamic obj;
@end


@interface Derived : Base
@property (strong, nonatomic) NSString *obj;
@end

@implementation Derived
@synthesize obj = obj_;
@end

Is this code correct? I am concerned that @synthesize appears twice. Is this creating two properties, or does the @synthesize declaration in Derived override the one in Base?

Edit: Changing @synthesize to @dynamic in Base makes more sense.

Edit: This requires iOS SDK 5.

Risky answered 16/8, 2011 at 22:36 Comment(3)
Out of curiosity, what do you expect to happen if you take an instance of Derived, cast it back to Base, and then assign a non-string object to the property? In other words, something like ((Base *)myDerived).obj = [NSNumber numberWithInt:3].Plebe
Re the "Out of curiosity" above, this is equivalent to saying, "What if you cast an instance of Derived as (UICollectionViewController *)myDerived and then... i.e., if you are casting something to a type that it is not, don't expect good results. This includes not being able to treat a subclass as though it was its superclass.Zendah
@KevinBallard thanks for your comment. Briefly and very clear.Shrunk
P
51

Subclasses can change types associated with methods. In general, a subclass may specialize a return type, and may make argument types more generic. There's actually a name for this but I can't remember what it is. Anyway, here's the rational:

Return types

If I have a class

@interface A
- (id)foo;
@end

and another class

@interface B : A
- (NSString *)foo;
@end

And I have an instance B* b, I can cast it down to A* and still conform to the type signature of the method -[A foo], because any NSString* is also an id.

However, I cannot make this more generalized. If instead I have

@interface A
- (NSString *)foo;
@end

@interface B : A
- (id)foo;
@end

And I have an instance B* b and I cast it down to A*, then the type of [(A*)b foo] is NSString * and yet the actual value may be any id, because that's the type I declared -[B foo] to be. This is a violation of the type system.

Arguments

If I have a class

@interface A
- (void)foo:(NSString *)obj;
@end

and another class

@interface B : A
- (void)foo:(id)obj;
@end

And I have an instance B* b and I cast it down to A*, then any valid argument to [(A*)b foo:obj] also conforms to the type of -[B foo:], because any NSString * is also an id.

However if I have the following

@interface A
- (void)foo:(id)obj;
@end

@interface B : A
- (void)foo:(NSString *)obj;
@end

And I have an instance B* b and I cast it down to A*, then I could pass any id to [(A*)b foo:obj], but the underlying class B only expects NSString*s. And thus I've violated the type system.

Properties

Here is the sticky point. When you declare the type of a property, you're declaring both the return type of the getter and the argument type of the setter. According to the above rules, this means you cannot change the type of a property, because in one of the two cases you'll be violating the type system.


The above is the theory. In practice, I have no idea if GCC or Clang enforce these constraints. It's possible that they assume the programmer knows best, and improperly generalizing or specializing a type will silently break the type system behind your back. You'll have to experiment. But if the compiler is truly correct then it will disallow generalizing return types and specializing arguments. And that means it will disallow changing the type of a property.

Even if the compiler allows it, you probably shouldn't do it. Silently breaking the type system is a great way to introduce bugs, and an indicator of poor architecture.

Plebe answered 16/8, 2011 at 23:0 Comment(4)
Great Answer, but unfortunately Clang doesn't enforce there rules.Blur
"There's actually a name for this but I can't remember what it is" -> Contravariant method argument types? en.wikipedia.org/wiki/…Kornher
@DiegoFreniche Yes, method arguments are contravariant and method return types are covariant.Plebe
Co-/contravariance of function return/argument types is the rule for function subtyping and is also a consequence of the Liskov Substitution Principle, if either of those is the thing.Lilli
F
1

You actually can't do that. For me I receive an error

property 'obj' attempting to use ivar 'obj_' declared in super class of 'Derived'

so I think It is obvious. And even if you not using @synthesize and defining functions yourself, then only Derived version is called (there is no such thing like function overloading or virtual functions in ObjC).

You have to review your class design.

Fadein answered 16/8, 2011 at 22:51 Comment(5)
You need to be using iOS SDK 5.Risky
OK, but still this is not the best approach.Fadein
This particular error is simply because you have a @synthesize in Derived that references an ivar declared in the superclass. What happens if you get rid of that second @synthesize?Plebe
Which compiler? GCC, LLVM-GCC, or Clang? Sounds like the compiler isn't enforcing type constraints as well as it should (see my answer for details).Plebe
To answer your question: LLVM/ClangRisky

© 2022 - 2024 — McMap. All rights reserved.