Objective-C ist im März 2013 laut des TIOBE Programming Community Index nach Java und C die drittpopulärste Programmiersprache der Welt. Das verwundert auch nicht, wurden doch neben tausenden Mac Apps 775.000 iPhone, iPad und iPod touch Apps in ihr entwickelt und über den App Store angeboten (Stand: Januar 2013, laut Apple). Mit über 40 Milliarden verzeichneten Downloads ist die Attraktivität hoch, Apps möglichst schnell auf den Markt zu bringen.
Unter einer kurzen Time-to-Market leidet jedoch oftmals die (interne) Qualität der Software. Lebt das Produkt länger und werden die technischen Schulden nicht getilgt, vervielfachen sich im Lauf der Zeit die Entwicklungskosten. Dabei ist es gar nicht so schwer, von Anfang an Qualität in die Software “einzubauen”. Die nun startende Blog Post Serie Clean Objective-C soll hierzu einen kleinen Beitrag leisten. Als Erstes beginnen wir mit…
Private Methoden in Objective-C
Mit privaten Methoden können wir generell die Details oder Verhaltensweisen einer Klasse vor anderen Klassen verbergen, d.h. schützen. Andere Klassen können Klientenklassen, Categories oder Unterklassen sein. Erweitern wir eine Klasse, müssen wir jedoch aufpassen, ihre Kapselung nicht versehentlich aufzubrechen. Dieser Blog Post illustriert letzteres Problem mit Hilfe des Einsatzes von Vererbung und zeigt einen Lösungsweg dafür auf, der natürlich auch für Categories gilt.
Von einer öffentlichen zu einer privaten Methode
Um eine Methode nicht als Bestandteil einer Klassen-API auszuweisen, deklariert man sie nicht im @interface
-Abschnitt sondern definiert sie im @implementation
-Bereich.
Dies wollen wir im folgenden Beispiel mit der Methode makeActivationSound
unserer Klasse Lightsaber
machen:
[code lang=”objc” highlight=”7″]
#import <Foundation/Foundation.h>
@interface Lightsaber : NSObject
– (void)switchOn;
– (void)makeActivationSound;
@end
[/code]
Wir müssen makeActivationSound
also aus unserer Schnittstellendefinition (Lightsaber.h
) entfernen:
[code lang=”objc”]
#import <Foundation/Foundation.h>
@interface Lightsaber : NSObject
– (void)switchOn;
@end
[/code]
Die Implementierung unserer Klasse (Lightsaber.m
) müssen wir nicht anpassen; sie sieht so aus:
[code lang=”objc” highlight=”10,11,12″]
#import "Lightsaber.h"
@implementation Lightsaber
– (void)switchOn {
[self makeActivationSound];
// …
}
– (void)makeActivationSound {
NSLog(@"BB-ZSHOOOO");
}
@end
[/code]
Somit kann eine private Methode wie makeActivationSound
nicht länger von einer anderen Klasse aufgerufen werden. Ein Aufruf aus einer Jedi-Klasse wie…
[code lang=”objc” highlight=”1″]
[[Lightsaber new] makeActivationSound];
[/code]
…ist nicht länger möglich:
[code light=”true” highlight=”1,2″]
Compiler error: Method ‘makeActivationSound’ is defined in
class ‘Lightsaber’ and is not visible.
[/code]
(Mittels der Methode performSelector
oder der Objective-C Runtime Bibliothek könnten wir natürlich immer noch unsere private Methode aufrufen.)
So weit, so gut. Wir können ja die Methode switchOn
aufrufen, um den Soundeffekt beim Einschalten des Lichtschwertes wahrnehmen zu können:
[code lang=”objc” highlight=”1″]
[[Lightsaber new] switchOn]; // -> BB-ZSHOOOO
[/code]
Ein unerwartetes Verhalten
Stellen wir uns nun vor, wir haben unser Lichtschwert verloren. Ein Padawan findet es in der Wüste. Da er technisch versiert ist, möchte er unser Lichtschwert um eine zweite “Lichtklinge” erweitern. Er legt daher die Unterklasse DoubleBladedLightsaber
an:
[code lang=”objc” highlight=”5″]
// DoubleBladedLightsaber.h
#import <Foundation/Foundation.h>
#import "Lightsaber.h"
@interface DoubleBladedLightsaber : Lightsaber
@end
[/code]
Die hinzugefügte Lichtklinge macht beim Einschalten statt eines BB-ZSHOOOO-Klangs einen BB-ZSHUUUU-Sound. Ohne zu wissen, wie der Klang der alten Lichtklinge erzeugt wird, beschließt der Padawan, dem neuen Lichtschwert eine BB-ZSHUUUU-Klangerzeugungsfunktionalität namens makeActivationSound
hinzuzufügen. Diese nutzt er beim Einschalten seines doppelseitigen Lichtschwerts, gemeinsam mit der existierenden Einschalt-Funktionalität:
[code lang=”objc” highlight=”6,7,8,9,11,12,13″]
// DoubleBladedLightsaber.m
#import "DoubleBladedLightsaber.h"
@implementation DoubleBladedLightsaber
– (void)switchOn {
[super switchOn];
[self makeActivationSound];
}
– (void)makeActivationSound {
NSLog(@"BB-ZSHUUUU");
}
@end
[/code]
Voller Neugier drückt er den Einschaltknopf, um den coolen BB-ZSHOOOO, BB-ZSHUUUU-Sound hören zu können…
[code lang=”objc” highlight=”1″]
[[DoubleBladedLightsaber new] switchOn];
// -> BB-ZSHUUUU
// -> BB-ZSHUUUU
[/code]
Aber was war das? Der Klang der alten Lichtklinge hat sich verändert! Sie klingt nicht mehr nach BB-ZSHOOOO sondern nach der neuen – nach BB-ZSHUUUU! Wie ist das möglich?
Die Methode switchOn
aus der Klasse Lightsaber
hat die Implementierung der Methode makeActivationSound
aus der Unterklasse DoubleBladedLightsaber
aufgerufen. Die Implementierung von makeActivationSound
aus der Klasse Lightsaber
wurde also durch die der Klasse DoubleBladedLightsaber
überschrieben. Private Methoden in Objective-C sind nicht so privat wie man es z.B. aus moderneren objektorientierten Programmiersprachen kennt. Sie sind semi-privat und verhalten sich polymorph!
Das Risiko… und seine Reduktion
Das birgt ein gewisses Risiko: Wir könnten (versehentlich) die Implementierung unserer erweiterten Klasse kompromittieren. Die Wahrscheinlichkeit, eine private Methode unwissentlich zu überschreiben, steigt, wenn wir eine Framework- oder Bibliotheksklasse erweitern, zu der wir den Source Code nicht besitzen. Apples Cocoa-Klassen kommen einem da schnell in den Sinn. Dem Padawan ist es mit unserem Lichtschwert so ergangen.
Um das Risiko des Überschreibens zu verringern, muss möglichst ein einzigartiges Präfix für private Methoden genutzt werden. Das naheliegendste Präfix ist vermutlich der Unterstrich (_
), doch den verwendet Apple schon für die Namen der privaten Cocoa-Methoden.
Somit sollte man sich für ein Kürzel des Unternehmensnamens und/oder für das Produkt entscheiden. In unserem Beispiel könnte das das Präfix AQN_LS_
für das Unternehmen akquinet und das Produkt Lightsaber sein:
[code lang=”objc” highlight=”6,10″]
#import "Lightsaber.h"
@implementation Lightsaber
– (void)switchOn {
[self AQN_LS_makeActivationSound];
// …
}
– (void)AQN_LS_makeActivationSound {
NSLog(@"BB-ZSHOOOO");
}
@end
[/code]
Nach dieser Änderung kann der Padawan beim Einschalten seines doppelseitigen Lichtschwerts endlich BZ-SHOOOO, BZ-SHUUUU hören. Möge die Macht mit ihm sein!
Ob eine private Methode versehentlich überschrieben wird, ist also davon abhängig, wie einzigartig ihr Name ist.
Zum Schluss sei noch erwähnt, dass Apple u.a. in den Coding Guidelines for Cocoa auf das beschriebene Problem hinweist.
tl;dr
- Da private Methoden in Objective-C überschrieben werden können, besteht die Gefahr, die Implementierung einer zu erweiternden Klasse zu kompromittieren.
- Verwende daher stets ein applikationsspezifisches, einzigartiges Präfix für die Namen privater Methoden.
- Ein einzelner Unterstrich (
_
) darf nicht als Präfix genutzt werden, da er für Cocoa-Klassen reserviert ist.