ref – http://blog.akquinet.de/2013/03/28/clean-objective-c-private-methods-in-objective-c/
http://www.friday.com/bbum/2009/09/11/class-extensions-explained/
A word about usage
When implementing a class, it is common to have a set of methods that only appear in the class’s @implementation. Often, they are spread around the @implementation and generally appear just above whatever method first uses the private method (if they were to appear below, the compiler will warn).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@implementation -(void)pvtMethod1 {} -(void)pvtMethod2 {} -(void)pvtMethod3 {} -(void)pvtMethod4 {} ... -(void)publicMethod { //calls pvtMethod1 //calls pvtMethod2 } @end |
Eventually, this becomes unwieldy and the developer will capture the private method’s declarations into a category declaration at the top of the implementation file. Something like:
1 2 3 4 5 6 7 8 |
@interface MyClass (SuperSecretInternalSauce) - (void) doMyPrivateThing; - (BOOL) canMyPrivateThingEatThis: (OtherClass *) aThing; @end @implementation MyClass ... @end |
The resulting problem is that the compiler will not check to make sure you have implemented all of the methods declared in that category. Nor will it catch, say, spelling errors in the method declarations in the implementation.
This is because a category with no corresponding implementation is an informal protocol in Objective-C. It is a set of method declarations that can optionally be implemented, often on a subclass of the class with the category declaration.
Because a class extension effectively extends the class’s primary interface, changing the above declaration to the following makes the declared methods have the same requirements as methods declared in the class’s oft public primary interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@interface RedLightSaber () {} -(void)semiPrivateMethods; @end @implementation RedLightSaber //warning will appear if 'semiPrivateMethods' not implemented - (void)makeActivationSound { NSLog(@"BB-ZSHOOOO"); } -(void) lightSaberCrystal { NSLog(@"%s", __FUNCTION__); } @end |
Background
One common complaint for many new to Objective-C is that the language lacks support for private methods within a class. There are compiler directives for instance variables: @private, @protected (the default) and @public. link
However, if you want to hide methods, there is no specific directives to help.
The Problem
Using private methods, we can generally hide the details or behaviors of a class from other classes. These other classes may be clients, Categories, or subclasses.
Compile-time private methods are supported; if a class doesn’t declare a method in its publicly available interface, then that method might as well not exist as far as your code is concerned.
In other words, you can achieve all of the various combinations of visibility desired at compilation time by organizing your project appropriately.
For example:
To avoid exposing a method as part of a class’s API, we do not declare it within the @interface section but within the @implementation section.
1 2 3 4 5 6 |
@interface Lightsaber : NSObject - (void)switchOn; - (void)makeActivationSound; @end |
makeActivationSound is current exposed. Let’s say we want to make this private, and not let others access this method. We so by removing it from the interface Lightsaber, and put it either in the “private extension” of the Lightsaber class, or simply just declare it as a method within the implementation file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#import <Foundation/Foundation.h> @interface Lightsaber : NSObject - (void)switchOn; @end <pre> #import "Lightsaber.h" @implementation Lightsaber - (void)switchOn { [self makeActivationSound]; // ... } - (void)makeActivationSound { NSLog(@"BB-ZSHOOOO"); } @end |
Now, a private method like makeActivationSound can no longer be called by another class. A method call from a Jedi class like:
1 |
[[Lightsaber new] makeActivationSound]; |
is no longer possible.
Compiler error: Method ‘makeActivationSound’ is defined in class
‘Lightsaber’ and is not visible.
Methods are semi private in Objective C
using performSelector to call private methods
Although if we use the performSelector method or the Objective-C Runtime library, we can still invoke private methods.
switchOn is the public method, which calls the private method makeActivationSound of the LightSaber class.
From ViewController, by using performSelector, we still able to manage the private method.
The result would be:
BB-ZSHOOOO
BB-ZSHOOOO
Hence there is no true private methods in Objective C.
Using subclasses to access private method
When child classes access the Lightsaber public method switchOn, it will access Lightsaber private method makeActivationSound.
Extending the base class
So say another developer comes in and want to extend the Lightsaber class into a child class called DoubleBladedLightsaber
1 2 3 4 5 6 |
// DoubleBladedLightsaber.h #import <Foundation/Foundation.h> #import "Lightsaber.h" @interface DoubleBladedLightsaber : Lightsaber @end |
The developer wants to over-ride the switchOn behavior, and therefore calls [super switchOn]. Then, he wants to add an additional functionality so he declares his own private method. And calls it right after using the base class’s switchOn.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#import "DoubleBladedLightsaber.h" @implementation DoubleBladedLightsaber - (void)switchOn { [super switchOn]; [self makeActivationSound]; //calls private method } //declare private method - (void)makeActivationSound { NSLog(@"BB-ZSHUUUU"); } @end |
UH OH! PROBLEM
[[DoubleBladedLightsaber new] switchOn];
What he gets is:
BB-ZSHUUUU
BB-ZSHUUUU
The 1st BB-ZSHUUUU is from [super swithcOn]. The switchOn in the base class then calls makeActivationSound. However, it sees that the child class have over-ridden its makeActivationSound, and thus, uses the child’s makeActivationSound method. This results in the output BB-ZSHUUUU, and not BB-ZSHOOOO.
The 2nd BB-ZSHUUUU is just a call to its own private method makeActivationSound. THEREFORE:
Private methods in Objective-C are not as private as in other modern object-oriented programming languages. They are semi-private and have polymorphic behavior.
In objective C, we can (accidentally) compromise the implementation of our extended class. The probability of unknowingly overriding a private method rises when we extend a framework or library class without having access to its source code.
To reduce the risk of accidental method overriding, we should add a prefix to the names of private methods that is as unique as possible.
For this reason, one should use an uppercase abbreviation of the company and/or the product name. In our example that could be the prefix AQN_LS_ for the company akquinet and the product Lightsaber:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#import "Lightsaber.h" @implementation Lightsaber - (void)switchOn { [self AQN_LS_makeActivationSound]; // ... } - (void)AQN_LS_makeActivationSound { NSLog(@"BB-ZSHOOOO"); } @end |
After this modification, the [super switchOn] will access its own class’s private method.
So, the risk of accidentally overriding a private method depends on the uniqueness of its name.
- As private methods can be overridden in Objective-C, there is a risk of compromising the implementation of an extended class.
- Therefore, always use an application-specific unique prefix for the names of private methods.
- A single underscore character (_) may not be used as a prefix, as this is reserved for Cocoa classes.