35

According to some official talk, a class in Objective-C should only expose public methods and properties in its header:

@interface MyClass : NSObject

@property (nonatomic, strong) MyPublicObject *publicObject;

- (void)publicMethod;

@end

and private methods/properties should be kept in class extension in .m file:

@interface MyClass()

@property (nonatomic, strong) MyPrivateObject *privateObject;

- (void) privateMethod;

@end

and I don't think there is a protected type for things that are private but accessible from subclasses. I wonder, is there anyway to achieve this, apart from declaring private properties/methods publicly?

jscs
  • 63,694
  • 13
  • 151
  • 195
hzxu
  • 5,753
  • 11
  • 60
  • 95
  • 2
    [this](http://stackoverflow.com/questions/3725857/protected-methods-in-objective-c) question is similar (actually identical) and the short answer is no, you can't. – Cashew Sep 28 '12 at 04:38

7 Answers7

35

One way to solve this is to re-declare the property in your subclass's class extension, and then add an @dynamic statement so that the compiler won't create an overriding implementation of that property. So something like:

@interface SuperClass ()

@property (nonatomic, strong) id someProperty;

@end

....


@interface SubClass ()

@property (nonatomic, strong) id someProperty;

@end

@implementation SubClass

@dynamic someProperty;

@end

This obviously isn't ideal because it duplicates a privately visible declaration. But it is quite convenient and helpful in some situations so I'd say evaluate on a case-by-case basis the dangers involved in this duplication vs. exposing the property in the public interface.

An alternative - that is used by Apple in UIGestureRecognizer - is to declare the property in a separate category header file explicitly named as "private" or "protected" e.g. "SomeClass+Protected.h". That way, other programmers will know they ought not import the file. But, if you don't control the code you're inheriting from, that's not an option.

ThomasW
  • 16,981
  • 4
  • 79
  • 106
Carl Veazey
  • 18,392
  • 8
  • 66
  • 81
  • 3
    i can't believe this.. are you serious? so you are saying that i'll have to duplicate the declaration of these private vars in every subclass??? that's so inconvenient.. there must be another way – abbood Apr 30 '13 at 08:17
  • 3
    not only are you declaring it again.. you are also writing it a third time with the `@dynamic` business.. that's writing a variable 3 times instead of one! – abbood Apr 30 '13 at 08:19
  • 4
    @abbood Well, if they're truly private, the subclasses shouldn't be using them anyway ;) But - I think the alternative of having a SuperClass+Protected header is viable - it's what Apple officially supports (by example at least). – Carl Veazey Apr 30 '13 at 08:22
  • oh.. sorry i confused `private` with `protected`.. so i simply want to declare `protected` variables in my superClass .m file, and have the subclasses inherit it without re-declaring it and this dynamic business.. is that possible? – abbood Apr 30 '13 at 08:30
  • what i'm trying to accomplish is not to pollute my .h file with variables and methods that only subclasses should know about.. not the whole world – abbood Apr 30 '13 at 08:38
  • @abood Yes, it's possible - make a separate header file for your protected properties and methods. See `UIGestureRecognizerSubclass.h` and the "subclassing notes" section of U.I.G.R.'s class reference.. – Carl Veazey Apr 30 '13 at 09:27
  • 1
    I [posted a full example](http://bootstragram.com/blog/simulating-protected-modifier-with-objective-c/) of how to implement stuff the same way Apple does in `UIGestureRecognizerSubclass.h` – Mick F Mar 19 '14 at 17:01
  • Isn't it possible now to just create a public interface with a (@)protected iVar? Not sure when (@)protected was released.. – Aaron Smentkowski Jan 14 '16 at 17:40
16

This is possible by using a class extension (not category) that you include in the implementation files of both the base class and subclasses.

A class extension is defined similar to a category, but without the category name:

@interface MyClass ()

In a class extension, you can declare properties, which will be able to synthesize the backing ivars (XCode > 4.4 automatic synthesis of the ivars also works here).

In the extension class, you can override/refine properties (change readonly to readwrite etc.), and add properties and methods that will be "visible" to the implementation files (but note that the properties and methods aren't really private and can still be called by selector).

Others have proposed using a seperate header file MyClass_protected.h for this, but this can also be done in the main header file using #ifdef like this:

Example:

BaseClass.h

@interface BaseClass : NSObject

// foo is readonly for consumers of the class
@property (nonatomic, readonly) NSString *foo;

@end


#ifdef BaseClass_protected

// this is the class extension, where you define 
// the "protected" properties and methods of the class

@interface BaseClass ()

// foo is now readwrite
@property (nonatomic, readwrite) NSString *foo;

// bar is visible to implementation of subclasses
@property (nonatomic, readwrite) int bar;

-(void)baz;

@end

#endif

BaseClass.m

// this will import BaseClass.h
// with BaseClass_protected defined,
// so it will also get the protected class extension

#define BaseClass_protected
#import "BaseClass.h"

@implementation BaseClass

-(void)baz {
    self.foo = @"test";
    self.bar = 123;
}

@end

ChildClass.h

// this will import BaseClass.h without the class extension

#import "BaseClass.h"

@interface ChildClass : BaseClass

-(void)test;

@end

ChildClass.m

// this will implicitly import BaseClass.h from ChildClass.h,
// with BaseClass_protected defined,
// so it will also get the protected class extension

#define BaseClass_protected 
#import "ChildClass.h"

@implementation ChildClass

-(void)test {
    self.foo = @"test";
    self.bar = 123;

    [self baz];
}

@end

When you call #import, it basically copy-pastes the .h file to where you are importing it. If you have an #ifdef, it will only include the code inside if the #define with that name is set.

In your .h file, you don't set the define so any classes importing this .h wont see the protected class extention. In the base class and subclass .m file, you use #define before using #import so that the compiler will include the protected class extension.

d4n3
  • 1,453
  • 14
  • 13
8

While the other answers are correct, I'd like to add...

Private, protected and public are available for instance variables as such:

@interface MyClass : NSObject {
@private
  int varA;

@protected
  int varB;

@public
  int varC;
}

@end
phatmann
  • 18,161
  • 7
  • 61
  • 51
Alex Smith
  • 468
  • 5
  • 22
  • 8
    These are instance variables, objc has no concept of class variables. – Tom Apr 30 '13 at 08:30
  • This is great to know. If you need to share instance variables with subclasses, @protected is the way to do it. – phatmann Feb 01 '15 at 11:59
  • this is great, but @properties are not instance variables. At most - they have a backing storage of such iVars, and even then, you cannot control the visibility of these iVars any better than controlling visibility of methods. – Motti Shneor Feb 23 '19 at 05:25
1

Your only choice is to declare it as public in the header file. If you want to at least keep some method separation, you can create a category and have all your protected methods and attributes there, but in the end everything will still be public.

#import "MyClass.h"

@interface MyClass (Protected)

- (void) protectedMethods;

@end
8vius
  • 5,786
  • 14
  • 74
  • 136
  • No need for a category. Make it a class extension by skipping the `… (Protected)`. – Regexident Apr 30 '13 at 08:35
  • @Regexident the whole point here, is to tall sub-classer that the protectedMethod is there for overriding. Naming the extension (Protected) is a perfect solution for that. – Motti Shneor Feb 23 '19 at 05:26
1

Simply create a .h file with your class extension. Import this into your .m files. Incidentally, this is a great way to test private members without breaking encapsulation (I'm not saying you should test private methods :) ).

// MyClassProtectedMembers.h
@interface MyClass()

@property (nonatomic, strong) MyPrivateObject *privateObject;
- (void) privateMethod;
@end

/////////////////

#import "MyClassProtectedMembers.h"

@implementation MyClass
// implement privateMethod here and any setters or getters with computed values
@end

Here's a gist of the idea: https://gist.github.com/philosopherdog/6461536b99ef73a5c32a

SmileBot
  • 19,393
  • 7
  • 65
  • 62
1

I see good answers for making properties visible, but I don't see exposing the methods addressed very clearly in any of these answers. Here is how I have successfully exposed private methods to the subclass using a Category:

SomeSuperClass.m:

@implementation SomeSuperClass

-(void)somePrivateMethod:(NSString*)someArgument {
    ...
}

SomeChildClass.h

@interface SomeChildClass : SomeSuperClass

SomeChildClass.m

@interface SomeSuperClass (exposePrivateMethod)
-(void)somePrivateMethod:(NSString*)someArgument;
@end

@implementation SomeChildClass

-(void)doSomething {
    [super somePrivateMethod:@"argument"];
}

@end
Chuck Krutsinger
  • 2,830
  • 4
  • 28
  • 50
0

That's because there's not even a real distinction between private and public. While the compiler may warn you about an interface missing a certain method or instance variable, your program will still work.

Scott Berrevoets
  • 16,921
  • 6
  • 59
  • 80