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.
Hinterlasse einen Kommentar
An der Diskussion beteiligen?Hinterlasse uns deinen Kommentar!