Die drei access control levels private, internal und public, die Apple mit der Xcode 6 beta 4 in Swift hinzufügte, sind ohne Frage sehr nützlich. Sie ermöglichen es, Implementierungsdetails zu verbergen (Information Hiding), und damit einen der Kernaspekte objektorientierter Programmierung zu nutzen.

Damit beginnen wir nun enthusiastisch alle Methoden als private zu kennzeichnen, die nur innerhalb der Klasse aufgerufen werden sollen. Die Klasse bleibt nach außen übersichichtlich und die Methodenauswahlliste von Xcode bietet von außen nur diejenigen Methoden zum Aufrufen an, die nicht private sind. Wunderbar. Oder?

So einfach ist das leider nicht, denn da gibt es oft einen Haken:

Verwendung von Selektoren

Es gibt (noch immer) viele APIs von Apple, die auf Selektoren setzen. Ein Beispiel ist UIBarButtonItem. Um zu definieren, was beim Drücken des Buttons passieren soll, gibt man dem initializer den Selektor einer Methode mit.

Beispiel:

UIBarButtonItem(image: UIImage(named: "search.png"), style: .Plain, target: self, action: "didTapSearchButton")

Der Selektor verweist auf die Methode:

private func didTapSearchButton() {
    //...
}

Die Methode ist private, weil sie zu den Implementierungsdetails gehört. Niemand soll auf die Idee kommen, sie von außerhalb der Klasse aufzurufen.

Dieser Code wird ohne Fehler und ohne Warnings gebaut. Zur Laufzeit kommt es beim Aufruf der didTapSearchButton Methode jedoch zu einem Crash:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyProject.MyClass didTapSearchButton]: unrecognized selector sent to instance 0x79670990'
*** First throw call stack:
(
    0   CoreFoundation                      0x013b4946 __exceptionPreprocess + 182
    1   libobjc.A.dylib                     0x0103da97 objc_exception_throw + 44
    2   CoreFoundation                      0x013bc5c5 -[NSObject(NSObject) doesNotRecognizeSelector:] + 277
    3   CoreFoundation                      0x013053e7 ___forwarding___ + 1047
    4   CoreFoundation                      0x01304fae _CF_forwarding_prep_0 + 14
    5   libobjc.A.dylib                     0x010537cd -[NSObject performSelector:withObject:withObject:] + 84
    6   UIKit                               0x020f123d -[UIApplication sendAction:to:from:forEvent:] + 99
    7   UIKit                               0x02461840 -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 139
    8   libobjc.A.dylib                     0x010537cd -[NSObject performSelector:withObject:withObject:] + 84
    9   UIKit                               0x020f123d -[UIApplication sendAction:to:from:forEvent:] + 99
    10  UIKit                               0x020f11cf -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 64
    11  UIKit                               0x02224e86 -[UIControl sendAction:to:forEvent:] + 69
    12  UIKit                               0x022252a3 -[UIControl _sendActionsForEvents:withEvent:] + 598
    13  UIKit                               0x0222450d -[UIControl touchesEnded:withEvent:] + 660
    14  UIKit                               0x0214160a -[UIWindow _sendTouchesForEvent:] + 874
    15  UIKit                               0x021420e5 -[UIWindow sendEvent:] + 791
    16  UIKit                               0x02107549 -[UIApplication sendEvent:] + 242
    17  UIKit                               0x0211737e _UIApplicationHandleEventFromQueueEvent + 20690
    18  UIKit                               0x020ebb19 _UIApplicationHandleEventQueue + 2206
    19  CoreFoundation                      0x012d81df __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
    20  CoreFoundation                      0x012cdced __CFRunLoopDoSources0 + 253
    21  CoreFoundation                      0x012cd248 __CFRunLoopRun + 952
    22  CoreFoundation                      0x012ccbcb CFRunLoopRunSpecific + 443
    23  CoreFoundation                      0x012cc9fb CFRunLoopRunInMode + 123
    24  GraphicsServices                    0x03f6424f GSEventRunModal + 192
    25  GraphicsServices                    0x03f6408c GSEventRun + 104
    26  UIKit                               0x020ef8b6 UIApplicationMain + 1526
    27  Besen                               0x0015a7ae top_level_code + 78
    28  Besen                               0x0015a7eb main + 43
    29  libdyld.dylib                       0x0380eac9 start + 1
    30  ???                                 0x00000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

Es crasht, weil die Methode vom UIBarButtonItem aufgerufen wird. Und das ist außerhalb der Klasse. Die Methode darf daher nicht private sein.

Bei Objective-C würde man die Methode in der .m Datei definieren und sie wäre von außerhalb nicht sichtbar, aber immer noch aufrufbar. Bei Swift geht das leider nicht. Die Methode muss von außerhalb sichtbar sein, damit sie aufgerufen werden kann.

Delegate Methoden

Es gibt noch eine Kategorie von Methoden, die man nicht private machen kann, obwohl sie zu Implementierungsdetails gehören: Delegate Methoden.

Beispiel:

//Methode aus dem UITableViewDataSource Protokoll
private override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

Wenn eine Klasse ein Protokoll adaptiert und die Methoden daraus implementiert, können diese Methoden nicht private sein. Dafür sorgt schon der Compiler, indem er den Buildvorgang mit einem Fehler beendet.

In Objective-C hätte man auch in diesem Fall die Methoden in der .m Datei als Implementierungsdetails versteckt.
In Swift bleibt einem nichts anderes übrig, als sie nach außen sichtbar und aufrufbar zu machen.

Was kann man tun?

Man kann alternative APIs verwenden.

BlocksKit zum Beispiel ist eine empfehlenswerte Sammlung von Categories (Objective-C Code), die oft benutze Standardkomponenten wie UIBarButtonItem oder UITextField erweitert, sodass man Blocks (in Swift Closures) anstelle von Selektoren und Delegates verwenden kann.

Damit bleiben die Implementierungsdetails wirklich innerhalb der Klasse versteckt.

Nachtrag vom 17.04.2015

Methoden, die über Selektoren aufgerufen werden (target/action pattern), lassen sich mit @objc private prefixen:

@objc private func didTapSearchButton() {
    //...
}

Damit kommt es nicht mehr zu einem runtime crash und die Methode ist nach außen unsichtbar.

 

0 Kommentare

Dein Kommentar

An Diskussion beteiligen?
Hinterlasse uns Deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.