Das Konzept der Zugriffskontrolle ermöglicht es uns, den Zugriff auf Typen, Funktionen und andere Deklarationen durch anderen Code einzuschränken. Swift bietet fünf verschiedene Ebenen der Zugriffskontrolle, und die vollständige Nutzung von ihnen kann entscheidend sein, um Programme zu schreiben, die klar getrennte Anliegen und eine robuste Struktur haben.

Wenn wir einen neuen Typ, eine neue Eigenschaft oder eine neue Funktion in Swift definieren, hat diese standardmäßig die Zugriffsebene internal. Das bedeutet, dass es für alle anderen Codes sichtbar ist, die sich innerhalb desselben Moduls befinden — z. B. eine App, eine Systemerweiterung, ein Framework oder ein Swift-Paket.

Nehmen wir als Beispiel an, wir erstellen eine Shopping-App und haben eine Klasse namens PriceCalculator definiert, mit der wir den Gesamtpreis für ein Array von Produkten berechnen können:

Da wir derzeit keine explizite Zugriffsebene angeben, ist unsere PriceCalculator -Klasse (und ihre calculatePrice -Methode) von überall in unserer App aus zugänglich. Wenn wir unsere neue Klasse jedoch mit anderen Modulen teilen möchten (wir könnten sie beispielsweise in einem Framework implementieren, das wir zwischen unserer Haupt-App und einer Erweiterung oder einer Apple Watch-Begleit-App teilen), müssen wir sie public damit sie in diesen externen Kontexten sichtbar ist:

Die obige Änderung reicht jedoch nicht aus. Während wir jetzt in der Lage sind, unsere Klasse außerhalb des Moduls zu finden, in dem sie definiert ist, können wir keine Instanzen davon erstellen — da ihr (impliziter) Initialisierer wie jeder andere Code standardmäßig internal ist. Um das zu beheben, definieren wir einen public Initialisierer, den wir leer lassen, da darin keine eigentliche Arbeit zu erledigen ist:

public class PriceCalculator { public init() {} ...}

Wir sind jetzt in der Lage, unser PriceCalculator sowohl innerhalb als auch außerhalb seines Moduls zu finden, zu initialisieren und aufzurufen — fantastisch. Aber lassen Sie uns nun sagen, dass wir es auch in Unterklassen unterteilen möchten, um es zu ändern oder neue Funktionen hinzuzufügen. Während das derzeit innerhalb eines eigenen Moduls möglich ist, ist es wieder etwas, was außerhalb davon verhindert wird.

Um das zu ändern, müssen wir Swifts derzeit offenste Zugriffssteuerungsebene verwenden, die entsprechend benannt ist open:

open class PriceCalculator { ...}

Mit der obigen Änderung können wir jetzt überall benutzerdefinierte Unterklassen von PriceCalculator erstellen, die neue Initialisierer, neue Eigenschaften und neue Methoden haben können. Hier ist, wie wir das verwenden könnten, um ein DiscountedPriceCalculator zu implementieren, mit dem wir ein gegebenes discount auf alle Preisberechnungen anwenden können:

Oben definieren wir eine brandneue Preisberechnungsmethode, aber es wäre wohl viel angemessener, die vorhandene calculatePrice -Methode, die wir stattdessen von unserer Basisklasse geerbt haben, zu überschreiben und zu ändern. Auf diese Weise gäbe es keine Verwirrung darüber, welche Methode aufgerufen werden soll, und wir könnten unsere beiden Klassen konsistent halten.

Um das tun zu können, müssen wir die ursprüngliche Deklaration — diesmal unsere calculatePrice Methodendeklaration — erneut als markieren open:

open class PriceCalculator { public init() {} open func calculatePrice(for products: ) -> Int { ... }}

Mit dem oben Genannten können wir jetzt calculatePrice frei überschreiben, anstatt eine separate Methode erstellen zu müssen:

Das sind also internal, public und open — die verwendet werden, um eine Deklaration schrittweise für die öffentliche Verwendung und Änderung zu öffnen. Aber wir können natürlich auch den anderen Weg gehen und Teile unseres Codes davor verbergen, entdeckt und verwendet zu werden. Auf den ersten Blick mag es fraglich erscheinen, welchen Wert das hat, aber es kann uns wirklich helfen, unsere API viel enger und fokussierter zu gestalten — was wiederum das Verstehen, Testen und Verwenden erleichtern kann.

Lassen Sie uns nun auf die andere Seite des Spektrums der Zugriffsebenen gehen und einen Blick auf die restriktivste Ebene werfen — private. Alle Typen, Eigenschaften oder Methoden, die als private markiert sind, sind nur innerhalb ihres eigenen Typs sichtbar (einschließlich Erweiterungen für diesen Typ, die in derselben Datei definiert sind).

Alles, was als privates Implementierungsdetail eines bestimmten Typs betrachtet werden sollte, sollte wohl als private markiert werden. Zum Beispiel war die discount -Eigenschaft unseres Preisrechners von früher wirklich nur für die Verwendung in einer eigenen Klasse gedacht – also machen wir diese Eigenschaft privat:

class DiscountedPriceCalculator: PriceCalculator { private let discount: Int ...}

Unsere vorherige Implementierung funktioniert weiterhin genauso wie zuvor, da discount in unserer DiscountedPriceCalculator -Klasse vollständig sichtbar bleibt. Wenn wir diese Sichtbarkeit jedoch geringfügig erweitern möchten, um auch andere in derselben Datei definierte Typen einzubeziehen, müssen wir fileprivate – was genau das tut, wonach es sich anhört, und eine Deklaration in der Datei privat hält, in der sie definiert ist:

class DiscountedPriceCalculator: PriceCalculator { fileprivate let discount: Int ...}

Mit der obigen Änderung können wir jetzt über verwandten Code, der in derselben Datei definiert ist, auf unsere discount —Eigenschaft zugreifen – z. B. diese Erweiterung auf UIAlertController, mit der wir problemlos eine Preisbeschreibung für eine Reihe von Produkten in einer Warnung anzeigen können:

Wenn es um freie Funktionen, Typen und Erweiterungen geht, verhalten sich private und fileprivate genau gleich. Sie unterscheiden sich nur, wenn sie auf Deklarationen angewendet werden, die innerhalb eines Typs definiert sind.

Zusammenfassend sind dies die fünf Ebenen der Zugriffskontrolle, die Swift derzeit bietet:

  • private hält eine Eigenschaft oder Funktion innerhalb ihres umschließenden Typs privat, einschließlich aller Erweiterungen dieses Typs, die in derselben Datei definiert sind. Wenn es auf einen Typ, eine Funktion oder eine Erweiterung der obersten Ebene angewendet wird, verhält es sich genauso wie fileprivate .
  • fileprivate macht eine Deklaration in der gesamten Datei sichtbar, in der sie definiert ist, und versteckt sie vor allem anderen Code.
  • internal ist die Standardzugriffsebene und macht eine Deklaration innerhalb des gesamten Moduls sichtbar, in dem sie definiert ist.
  • public zeigt eine Funktion, einen Typ, eine Erweiterung oder eine Eigenschaft außerhalb ihres Moduls an.
  • open ermöglicht das Unterklassifizieren einer Klasse und das Überschreiben einer Funktion oder Eigenschaft außerhalb ihres Moduls.

Im Allgemeinen ist es oft am besten, mit der restriktivsten Zugriffsebene zu beginnen, die eine bestimmte Deklaration praktisch haben kann, und die Dinge dann später zu öffnen, wenn dies erforderlich ist. Auf diese Weise schränken wir die Interaktionsmöglichkeiten zwischen unseren verschiedenen Typen und Funktionen ein, was auf den ersten Blick schlecht erscheinen mag, aber oft wirklich wichtig ist, um wartbare und gut strukturierte Systeme aufzubauen.

Danke fürs Lesen! 🚀

(Beachten Sie, dass in diesem Artikel nicht auf mutationsspezifische Zugriffsmodifikatoren wie private(set) eingegangen wurde. Diese werden in Zukunft in einem weiteren Basics-Artikel behandelt.)

Unterstützen Sie Swift von Sundell, indem Sie sich diesen Sponsor ansehen:

Instabug: Lösen Sie Fehler, Abstürze und andere Probleme viel schneller mit den detaillierten Stack-Traces, Netzwerkprotokollen und UI-Ereignissen, die Instabug automatisch an jeden Fehlerbericht anhängt. Wird sowohl von mir als auch von Tausenden von iOS-Entwicklungsteams auf der ganzen Welt verwendet. Probieren Sie es kostenlos aus und integrieren Sie es mit nur einer einzigen Codezeile.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.