5

I am attempting to create an abstract class and inherit some of its properties in a subclass. If I leave the properties in the abstract class' header file, all of the properties are accessible. The problem is that the instance of the subclass can also access those properties, which is not always desirable in my case.

For instance, I have a delegate in my abstract class that sends down button presses to its sub class. I realize that this may not be the best way of structuring inheritance, so other suggestions are welcome. However, I would still like to know how my subclass can inherit some properties from its superclass without making all of those properties available in its instance. Thanks in advance!

Here is some example code below:

@interface AbstractClass : UIView

@property (nonatomic, strong) id<ButtonDelegate>buttonDelegate;

@end

…

@protocol ButtonDelegate

@required
- (void) buttonWasPressed;

@end

…

@interface SubClass() <ButtonDelegate>

- (id)init {
    self = [super init];
    if (self) {
        self.buttonDelegate = self;
    }
    return self;
}

-(void) buttonWasPressed {
    [self doSomething];
}

…

@implementation ViewController

- (void)viewDidLoad {
    SubClass *subClass = [[SubClass alloc] init];
    subClass.buttonDelegate = self; // THIS IS NOT DESIRABLE
}
Chris
  • 1,663
  • 1
  • 15
  • 19
  • 4
    Take a look at how Apple does this with `UIGestureRecognizer`. – rmaddy Oct 11 '13 at 14:53
  • I'm sorry, I should have been more thorough with what I have tried. I looked at Apple's implementation (i.e. a separate header file for private properties) but it is not an optimal solution being the properties in my Abstract class are essential for the subclass to function properly. It also feels like the properties are detached from the classes. – Chris Oct 11 '13 at 15:14
  • 1
    The problem is that Objective-C does not support protected methods or properties. That is why Apple uses a separate header file. – rmaddy Oct 11 '13 at 15:16
  • I think you hit the nail on the head when saying "Objective-C does not support protected methods or properties". – Chris Oct 11 '13 at 16:54

6 Answers6

4

Do like UIGestureRecognizer does.

  1. All public properties and methods goes into UIGestureRecognizer.h

  2. All protected properties and methods goes into UIGestureRecognizerSubclass.h. Only import this in the *.m-files. Never include it in any public header.

  3. All private properties and methods goes into *.m-files. Use the @interface ClassName ()

Example https://gist.github.com/hfossli/8041396

hfossli
  • 22,616
  • 10
  • 116
  • 130
  • Thanks for your response. I saw this example but I would like to keep related properties within their classes rather than having separate files. – Chris Oct 11 '13 at 17:06
  • 1
    Well, this isn't the language you're previously used. There is different ways of acheiving the same thing across languages. This is one of those things. What's the hazzle with importing the subclass-header anyways? – hfossli Oct 12 '13 at 16:42
  • @hfossli Are you using a category for UIGestureRecognizerSubclass like apple did? Seems the only way to provide storage for those 'protected properties' is with associated objects. No? – Bob Spryn Dec 18 '13 at 21:33
  • Or also adding the properties to the parent class in a private category. Blurgh. – Bob Spryn Dec 18 '13 at 22:07
  • @BobSpryn I've added an example which explains better. – hfossli Dec 19 '13 at 15:54
  • @hfossli Right that's what I figured out. Having to redeclare all properties. A little bit ugly, but it works. Thanks! – Bob Spryn Dec 19 '13 at 17:06
  • Yeah, I wish there was some way to avoid that. Well, in the old days (a couple of years ago), we had to synthesize every property and declare it as ivars, and even retain-release them ourselves. :D – hfossli Dec 19 '13 at 20:20
4

how to my subclass can inherit some properties from its superclass without making all of those properties available in its instance

What is the problem with this?

#import <Foundation/Foundation.h>

@interface Animal : NSObject
{
    @protected
    NSString *name; // default access. Only visible to subclasses.
}
@end

@implementation Animal
-(NSString*)description {
    return name;
}
@end

@interface Cow : Animal
@end

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        name   = @"cow";
    }
    return self;
}
@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        NSLog(@"%@", cow); // prints the name through internal access
        // error accessing from the outside: NSLog(@"%@", cow.name);

        Animal *animal = [Animal new];
        // error accessing from the outside: NSLog(@"%@", animal.name);
    }
}

Maybe I misunderstood the question, you say

Creating properties only visible to subclass in Objective-C

and then

The problem is that the instance of the subclass can also access those properties

Which one is it?

Jano
  • 62,815
  • 21
  • 164
  • 192
  • The question is "Creating properties only visible to subclass in Objective-C" and you clearly answered it. Since no post (at least that I found) on stackoverflow mentioned using protected, is there any downside to using it rather than using the method Apple uses? – Chris Oct 11 '13 at 17:05
  • Also, is there any way to create a protected property rather than ivar? – Chris Oct 11 '13 at 17:18
  • 1
    ivars are atomic (accessible from one thread at a time). This means they are around 4 times slower in multithread because of the lock, but faster in single thread because of the direct access. This time is insignificant for infrequent access. You can't use protected and property together. You can write { @ protected NSString *_name; } @ property (nonatomic,strong) NSString *name; and the compiler will use _name instead generating a new variable, but it won't be protected. @ protected is the default for variables inside brackets. @ public has same attributes as a default @ property. – Jano Oct 11 '13 at 17:59
  • Thanks for the reply. I had no idea that ivars are atomic, thus 4 times slower in multithreads. So by objective-c's standards, there is no such thing as a protected property? Maybe that is why even Apple uses a work around for protected properties. – Chris Oct 11 '13 at 21:51
  • Most code runs in the main thread, which is serial, so it doesn't matter, but many consider a design mistake making atomic the default. There are no protected properties. If you are curious, I wrote quite a few combinations of variables here: http://stackoverflow.com/a/14906215/412916 – Jano Oct 11 '13 at 23:23
  • The lesson here is that there is not such thing as protected properties. Placing the properties in a separate header file, as suggested by hfossli and rmaddy, is a workaround. However, to keep classes tightly bound to their variables and behaviors, I opted to use Jano's suggestion. I may reconsider the method if multithreading becomes an issue but as of now, this is more desirable. Thanks for everyone's help! – Chris Oct 14 '13 at 14:59
1

Create an empty category on top of your implementation file (.m):

@interface AbstractClass()

@property (nonatomic, strong) id<ButtonDelegate>buttonDelegate;

@end

In that way, your subclass will inherit and can access that property, but not other external classes because it's not in the header.

Antonio MG
  • 20,382
  • 3
  • 43
  • 62
  • it wont be visible for subclass, no? – medvedNick Oct 11 '13 at 14:53
  • 2
    well, I've just tried it for myself too, and it is not visible :) subclass imports abstract class's header, but this category is in .m . See my question [here](http://stackoverflow.com/questions/15272843/how-to-make-inherited-class-be-able-to-see-parents-hidden-methods-in-objective), I had the similar problem – medvedNick Oct 11 '13 at 15:01
  • I tried this solution and it did not work for me. This seems the best way to make a private property but its scope is limited its class. – Chris Oct 11 '13 at 15:15
  • @medvedNick, I checked out your question and saw the answer from bbum (a well-respected Objective-C developer). I would like to know if there is any way of accomplishing this without a separate header file tagged as Private. – Chris Oct 11 '13 at 15:19
  • I just tried the code once again and it's visible for me. In my code, VC2 is a subclass of VC3, VC3 declares a property in the interfaz of the .m, and VC2 can see it – Antonio MG Oct 11 '13 at 15:25
  • 1
    @AntonioMG, I tried again and the subclass is not seeing properties declared inside of .m's interface AbstractClass(). – Chris Oct 11 '13 at 16:58
1

I don't think there is any way to achieve this using property declaration.

Either a property be visible for all (declared in .h file) or it will be invisible for all (declared in .m file using category)

I guess one way is declaring public/protected variable in .h file class declaration:

@interface AbstractClass : UIView {

   ...

   id<ButtonDelegate>buttonDelegate;

   ...
}
@end

I am not sure about this, but give a try.

Mrunal
  • 13,982
  • 6
  • 52
  • 96
1

I see one approach that can fit your problem, however, it is pretty rude. Use Antonio's suggestion and create the private category with the property. As you've mentioned, it's scope is limited to the .m file. So you can put your subclasses into that file. This will be hard to read the code if subclasses are huge, but this is the only way for you as far as I understand.

EDIT: well, I have another solution. Copy

@property (nonatomic, strong) id<ButtonDelegate>buttonDelegate;

to all your subclasses. This will give you a warning about the absence of the property's @synthesize, but should work. I'd prefer this, if subclasses wont be changed or added often.

Let me describe how it would work.

We add a property into the Abstract class, and it is hidden for all (even for subclasses):

// .m file

@interface Abstract ()

@property (nonatomic, strong) id<ButtonDelegate> buttonDelegate;

@end


@implementation Abstract

@synthsize buttonDelegate;

@end;

But due to runtime features of Objective-C we still can call for that property, and there will not be any runtime error, only compiler warning.

To get rid of that warning and to add an ability to autocomplete, we add property without @synthsize into all subclasses:

@interface MySubclass : Abstract

@property (nonatomic, strong) id<ButtonDelegate> buttonDelegate;

@end

This will tell the compiler that there is such a property somewhere. There will be also one warning about the absence of @synthesize, but Xcode will still could autocomplete if you write something like

MySubclass *subclass = ...
subclass.butto...
medvedNick
  • 4,512
  • 4
  • 32
  • 50
  • Correct me if I'm wrong, but would't that just make the property of the subclass and not the superclass/abstract class? I ask because it sounds like an interesting solution. – Chris Oct 11 '13 at 17:00
  • The subclasses are quite "fixed," so to speak. Using your solution while declaring the the ivar as protected in the superclass, as suggested by jano, actually works close to a protected property. Xcode 5 gives a warning of "Autosynthesized property 'buttonDelegate' will used synthesized instance variable '_buttonDelegate', not existing instance variable 'buttonDelegate'." This compiles without error. To get rid of the warning, I synthesized the property as you suggested. Ideal would be to declare the property as protected in the AbstractClass, but this is not a bad work around. – Chris Oct 11 '13 at 17:23
  • @Chris no, that would work as property of an abstract class, so the subclasses will still have it. See my edit on question – medvedNick Oct 11 '13 at 21:12
  • thank you for your explanation, it makes complete sense. However, I do not want some properties inherited from the superclass in the subclass to be accessible in the subclass' instance. subclass.buttonDelegate is not desirable in this case. – Chris Oct 11 '13 at 21:48
0

It can not be done. There is no private or protected in objective-c. Stuff declared in the .m file "private" interface is only visible to that class and not in any subclass. Also you can always use your "private" properties/methods from outside if you want, although it would be bad practice to do so.

Peter Segerblom
  • 2,773
  • 1
  • 19
  • 24