Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding @property declarations in Objective-C

Tags:

objective-c

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.

like image 466
titaniumdecoy Avatar asked Aug 16 '11 22:08

titaniumdecoy


2 Answers

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.

like image 89
Lily Ballard Avatar answered Sep 28 '22 08:09

Lily Ballard


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.

like image 30
Max Avatar answered Sep 28 '22 06:09

Max