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.

3
  • 2
    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]. Commented Aug 16, 2011 at 22:43
  • 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. Commented Dec 29, 2015 at 19:53
  • @KevinBallard thanks for your comment. Briefly and very clear. Commented Sep 20, 2016 at 15:19

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Great Answer, but unfortunately Clang doesn't enforce there rules.
"There's actually a name for this but I can't remember what it is" -> Contravariant method argument types? en.wikipedia.org/wiki/…
@DiegoFreniche Yes, method arguments are contravariant and method return types are covariant.
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.
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.

5 Comments

You need to be using iOS SDK 5.
OK, but still this is not the best approach.
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?
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).
To answer your question: LLVM/Clang

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.