3

I have a class called AbstractBook, which has a property:

@property(strong) AbstractPage *page;

Now say I have a subclass of AbstractBook called WhiteBook and a subclass of AbstractPage called WhitePage. I want the WhiteBook class to inherit the page object, but in this class, I want it to be the specific WhitePage class instead of AbstractPage.

So, in WhiteBook, I just redeclared the property as WhitePage:

@property(strong) WhitePage *page;

However, I am getting a warning: WhitePage is incompatible with AbstractPage inherited from AbstractBook. What is the right way to implement what I'm going for?

Snowman
  • 31,411
  • 46
  • 180
  • 303

4 Answers4

5

What you want to do is fundamentally impossible. Don't forget that this:

@property(strong) AbstractPage *page;

declares a getter and a setter.

Generally speaking (this is for OO programming generally, not just Objective-C), there is no problem with an override getter returning a subclass of the return type of the super class implementation because this does not break the API contract. In your case, an instance of WhitePage is also an AbstractPage.

However, the setter is different. You can't restrict the type of the parameter because your subclass must be useable anywhere where an AbstractBook is used, so code that invokes the setter on something that it thinks is an AbstractBook is entitled to pass in an instance of any AbstractPage subclass, because that is what the API says.

I think what I would do is add a method to AbstractBook called something like

-(bool) isLegalPage: (AbstractPage) aPage;

that returns true always in the base class but true only for white pages in WhiteBook. Then I would manually implement the setter as follows:

-(void) setPage: (AbstractPage*) aPage
{
    if (![self isLegalPage: aPage])
    {
        // throw an exception or do other error notification
    }
    else
    {
        Do the assignment according to reference count/ARC/GC model
    }
}

Document that the setter will throw an exception if the page is not legal and that people should use the isLegalPage: method to test this.

The alternative id to have a read only property and use different setters in the base class and subclass, or don't have a setter at all in the base class.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
3

There is no property type covariance in Objective C, so if you want a property returning a subclass of AbstractPage, you need to define a separate property, say whitePage, in your WhiteBook subclass. You can return the same value from your page property, too, and it would work. Moreover, if your users call methods on the AbstractPage*, they would not even need to cast it to WhitePage*.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • So create a new property in my subclass called whitePage, and implement a custom getter method which returns the superclass object? – Snowman Jul 23 '12 at 15:22
  • @mohabitar Yes, that would be one solution. If the base class is abstract, do not synthesize the `page` property: this will let each subclass of the book to use a properly typed ivar for its own subclass of the page. – Sergey Kalinichenko Jul 23 '12 at 15:29
  • Wait don't synthesize in the abstract class? Then how would I set it initially? I don't want to set the specific whitePage property, I just want to set the AbstractPage page, while the getter returns the specific type.. – Snowman Jul 23 '12 at 15:33
  • @mohabitar You can definitely reuse the synthesized property from the abstract class, but then you'd need to cast the variable in your code if you want to access the properties of the page from ivar, or use the property that makes the cast for you. – Sergey Kalinichenko Jul 23 '12 at 15:37
2

Remember to program to the interface not implementation.

======================

Option: 1

If your Whitebook truly is a descendant of AbstractBook you don't need to make WhiteBook have a new property called "page", since your AbstractBook already declares it your WhiteBook gets it for free.

As long as your WhitePage inherits from AbstractPage you should be able to cast your page object as a WhitePage. Then you get all original functionality of AbstractPage, plus that of WhitePage.

So you shouldn't need to re-declare page in Whitebook. If you go to your Whitebook.h file and delete this line

@property(strong) WhitePage *page;

Then go into your Whitebook.m file and type self.page you shouldn't get any compile warnings. Then you can cast page as WhitePage and you should be good to go.

WARNING: This means you will need to cast page as a WhitePage where ever you use it, which is not ideal.

======================

Option: 2

You may also try leaving the code as you have it, but make sure to synthesize the "page" prop again in WhiteBook.

======================

Option: 3

You may also try leaving the code as you have it, but make sure to use @dynamic for the "page" prop in WhiteBook. Then implement the getter yourself and return a WhitePage instead of an AbstractPage.

jdog
  • 10,351
  • 29
  • 90
  • 165
  • I think I'm gonna go with option 3. However, would it be ok to have the property synthesized in the abstract class and dynamic in the subclass? – Snowman Jul 23 '12 at 15:25
  • Option 3 would be exceedingly outside the norm. – bbum Jul 23 '12 at 15:27
  • Really? So which would be the standard? I could use option 2, but then the warning would stick around right? – Snowman Jul 23 '12 at 15:28
  • Either create a second, more specific, property or use a return type of `(id)` (and file a bug against Objective-C asking for co-variance support). – bbum Jul 23 '12 at 17:38
0

How to override a superclass' property with more specific types?

Normally if you use @synthesize in your AbstractBook class and @dynamic in your WhiteBook class, it should work fine (unless, of course, you use the AbstractPage synthesized method to assign a non-WhitePage to page).

Community
  • 1
  • 1
Ilod
  • 146
  • 2