According to the TIOBE Programming Community Index (March 2013), Objective-C is the third most-popular programming language, behind Java and C. This is not surprising, as Objective-C is used to develop the thousands of Mac apps and 775,000 apps for iPhone, iPad, and iPod touch that are distributed via the App Store (Apple, as of January 2013). With more than 40 billion downloads, getting apps on the market as quickly as possible is highly attractive.
Unfortunately, the software’s (internal) quality often suffers from a short time to market. If the product’s lifecycle is longer than expected and technical debt is not paid off, development costs rise continuously. However, it is not that hard to build quality into the software right from the beginning. The new Clean Objective-C blog post series will demonstrate this. So, let’s get started…
Private Methods in Objective-C
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. If we extend a class, we have to make sure not to accidentally break its encapsulation. This blog post illustrates the latter problem by utilizing inheritance, and presents a solution (which is of course also valid for Categories).
From Public to Private Methods
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.
We want to do this with the method makeActivationSound
of our class Lightsaber
:
#import <Foundation/Foundation.h> @interface Lightsaber : NSObject - (void)switchOn; - (void)makeActivationSound; @end
We need to remove makeActivationSound
from our interface definition (Lightsaber.h
):
#import <Foundation/Foundation.h> @interface Lightsaber : NSObject - (void)switchOn; @end
We do not need to alter the implementation of our class (Lightsaber.m
); here it is:
#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…
[[Lightsaber new] makeActivationSound];
…is no longer possible:
Compiler error: Method 'makeActivationSound' is defined in class 'Lightsaber' and is not visible.
(Using the performSelector
method or the Objective-C Runtime library, we can still invoke private methods.)
So far, so good. We can call the switchOn
method to see the turn-on sound effect of the lightsaber:
[[Lightsaber new] switchOn]; // -> BB-ZSHOOOO
An Unexpected Behavior
Just imagine we have lost our lightsaber. A padawan finds it in the desert. As he is technically well-versed, he wants to extend our lightsaber with a second plasma blade. Therefore, he creates the subclass DoubleBladedLightsaber
:
// DoubleBladedLightsaber.h #import <Foundation/Foundation.h> #import "Lightsaber.h" @interface DoubleBladedLightsaber : Lightsaber @end
When switched on, the new plasma blade makes a BB-ZSHUUUU sound instead of BB-ZSHOOOO. Without knowing how the sound of the old plasma blade is created, the padawan decides to add a BB-ZSHUUUU sound effect creation functionality which he names makeActivationSound
. This one he uses together with the existing switch-on functionality when turning on the double-bladed lightsaber:
// DoubleBladedLightsaber.m #import "DoubleBladedLightsaber.h" @implementation DoubleBladedLightsaber - (void)switchOn { [super switchOn]; [self makeActivationSound]; } - (void)makeActivationSound { NSLog(@"BB-ZSHUUUU"); } @end
Bursting with curiosity, he pushes the activation button to hear the cool BB-ZSHOOOO, BB-ZSHUUUU sounds…
[[DoubleBladedLightsaber new] switchOn]; // -> BB-ZSHUUUU // -> BB-ZSHUUUU
But what was that? The sound of the old plasma blade has changed! It is no longer producing BB-ZSHOOOO but instead sounds like the new blade: BB-ZSHUUUU! How is that possible?
The method switchOn
of the class Lightsaber
has called the implementation of the method makeActivationSound
of the subclass DoubleBladedLightsaber
. The implementation of makeActivationSound
of the class Lightsaber
was overriden by the class DoubleBladedLightsaber
. 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.
The Risk… and Its Reduction
That poses a certain risk: 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. (Apple’s Cocoa classes immediately come to mind.) Using our lightsaber, the innocent padawan has fallen into this “trap”.
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. The most obvious prefix is probably an underscore (_
) but that is already used by Apple for its private Cocoa methods.
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:
#import "Lightsaber.h" @implementation Lightsaber - (void)switchOn { [self AQN_LS_makeActivationSound]; // ... } - (void)AQN_LS_makeActivationSound { NSLog(@"BB-ZSHOOOO"); } @end
After this modification, the padawan can finally hear a BZ-SHOOOO, BZ-SHUUUU when switching on his double-bladed lightsaber. May the force be with him!
So, the risk of accidentally overriding a private method depends on the uniqueness of its name.
Finally, it should be mentioned that Apple does point out the problem, e.g. in Coding Guidelines for Cocoa.
tl;dr
- 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.
Great tip, thanks! I’ve used the ability to override private methods for tests objects already, but haven’t given a single thought to the risk this poses.
Hey there, You contain done a top occupation. I will certainly digg it and personally plug to my buddies. I’m trusty they will probably be benefited from this website.
Reblogged this on Sutoprise Avenue, A SutoCom Source.