Clean Objective-C: Private Methoden in Objective-C

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:

#import <Foundation/Foundation.h>
 
@interface Lightsaber : NSObject
 
- (void)switchOn;
 
- (void)makeActivationSound;
 
@end

Wir müssen makeActivationSound also aus unserer Schnittstellendefinition (Lightsaber.h) entfernen:

#import <Foundation/Foundation.h>
 
@interface Lightsaber : NSObject
 
- (void)switchOn;
 
@end

Die Implementierung unserer Klasse (Lightsaber.m) müssen wir nicht anpassen; sie sieht so aus:

#import "Lightsaber.h"
 
@implementation Lightsaber
 
- (void)switchOn {
    [self makeActivationSound];
    // ...
}
 
- (void)makeActivationSound {
    NSLog(@"BB-ZSHOOOO");
}
 
@end

Somit kann eine private Methode wie makeActivationSound nicht länger von einer anderen Klasse aufgerufen werden. Ein Aufruf aus einer Jedi-Klasse wie…

[[Lightsaber new] makeActivationSound];

…ist nicht länger möglich:

Compiler error: Method 'makeActivationSound' is defined in
class 'Lightsaber' and is not visible.

(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:

[[Lightsaber new] switchOn]; // -> BB-ZSHOOOO

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:

// DoubleBladedLightsaber.h
#import <Foundation/Foundation.h>
#import "Lightsaber.h"
 
@interface DoubleBladedLightsaber : Lightsaber
@end

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:

 
// DoubleBladedLightsaber.m
#import "DoubleBladedLightsaber.h"
 
@implementation DoubleBladedLightsaber
 
- (void)switchOn {
    [super switchOn];
    [self makeActivationSound];
}
 
- (void)makeActivationSound {
    NSLog(@"BB-ZSHUUUU");
}
 
@end

Voller Neugier drückt er den Einschaltknopf, um den coolen BB-ZSHOOOO, BB-ZSHUUUU-Sound hören zu können…

[[DoubleBladedLightsaber new] switchOn];
// -> BB-ZSHUUUU
// -> BB-ZSHUUUU

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:

 
#import "Lightsaber.h"
 
@implementation Lightsaber
 
- (void)switchOn {
    [self AQN_LS_makeActivationSound];
    // ...
}
 
- (void)AQN_LS_makeActivationSound {
    NSLog(@"BB-ZSHOOOO");
}
 
@end

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.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s