Swift Language
Protokolle
Suche…
Einführung
Mit Protokollen können Sie festlegen, wie ein Objekt verwendet werden soll. Sie beschreiben eine Reihe von Eigenschaften und Methoden, die eine Klasse, Struktur oder Aufzählung bereitstellen soll, obwohl Protokolle keine Einschränkungen für die Implementierung aufwerfen.
Bemerkungen
Ein Swift-Protokoll ist eine Sammlung von Anforderungen, die konforme Typen implementieren müssen. Das Protokoll kann dann an den meisten Stellen verwendet werden, an denen ein Typ erwartet wird, beispielsweise Arrays und allgemeine Anforderungen.
Protokollmitglieder haben immer das gleiche Zugriffsmerkmal wie das gesamte Protokoll und können nicht separat angegeben werden. Obwohl ein Protokoll den Zugriff mit den Anforderungen für Getter oder Setter beschränken könnte, wie in den obigen Beispielen.
Weitere Informationen zu Protokollen finden Sie unter Die Swift-Programmiersprache .
Objective-C-Protokolle ähneln den Swift-Protokollen.
Protokolle sind auch mit Java-Schnittstellen vergleichbar.
Protokoll-Grundlagen
Über Protokolle
Ein Protokoll gibt Initialisierer, Eigenschaften, Funktionen, Subskripte und zugehörige Typen an, die für einen Swift-Objekttyp (Klasse, Struktur oder Aufzählung) erforderlich sind, der dem Protokoll entspricht. In einigen Sprachen werden ähnliche Ideen für Anforderungsspezifikationen für nachfolgende Objekte als "Schnittstellen" bezeichnet.
Ein deklariertes und definiertes Protokoll ist an und für sich ein Typ mit einer Signatur der angegebenen Anforderungen, die der Art und Weise ähnelt, in der Swift-Funktionen ein Typ sind, der auf der Signatur von Parametern und Rückgaben basiert.
Swift-Protokollspezifikationen können optional, explizit erforderlich und / oder vorgegebene Implementierungen über eine als Protocol Extensions bezeichnete Einrichtung sein. Ein Swift-Objekttyp (Klasse, Struktur oder Aufzählung), der ein Protokoll erfüllen möchte, das mit Erweiterungen für alle seine spezifizierten Anforderungen konkretisiert wird, muss lediglich den Wunsch nach Konformität erfüllen, um die vollständige Konformität zu gewährleisten. Die standardmäßige Implementierungsfunktion von Protocol Extensions kann ausreichen, um alle Verpflichtungen zur Einhaltung eines Protokolls zu erfüllen.
Protokolle können von anderen Protokollen vererbt werden. In Verbindung mit Protocol Extensions bedeutet dies, dass Protokolle als ein wesentliches Merkmal von Swift angesehen werden können und sollten.
Protokolle und Erweiterungen sind wichtig, um die umfassenderen Ziele und Ansätze von Swift hinsichtlich der Flexibilität bei der Programmgestaltung und der Entwicklungsprozesse zu realisieren. Der Hauptzweck der Swift-Protokoll- und Erweiterungsfunktion besteht in der Vereinfachung des kompositorischen Designs in der Programmarchitektur und -entwicklung. Dies wird als protokollorientierte Programmierung bezeichnet. Knusprige Oldtimer halten dies für einen Fokus auf OOP-Design.
Protokolle definieren Schnittstellen, die von jeder Struktur , Klasse oder Enumeration implementiert werden können:
protocol MyProtocol {
init(value: Int) // required initializer
func doSomething() -> Bool // instance method
var message: String { get } // instance read-only property
var value: Int { get set } // read-write instance property
subscript(index: Int) -> Int { get } // instance subscript
static func instructions() -> String // static method
static var max: Int { get } // static read-only property
static var total: Int { get set } // read-write static property
}
In Protokollen definierte Eigenschaften müssen entweder als { get }
oder { get set }
annotiert werden. { get }
bedeutet, dass die Eigenschaft abrufbar sein muss und daher als jede Art von Eigenschaft implementiert werden kann . { get set }
bedeutet, dass die Eigenschaft sowohl einstellbar als auch abrufbar sein muss.
Eine Struktur, die Klasse oder Enum ein Protokoll entsprechen kann:
struct MyStruct : MyProtocol {
// Implement the protocol's requirements here
}
class MyClass : MyProtocol {
// Implement the protocol's requirements here
}
enum MyEnum : MyProtocol {
case caseA, caseB, caseC
// Implement the protocol's requirements here
}
Ein Protokoll kann auch eine Standardimplementierung für jede seiner Anforderungen durch eine Erweiterung definieren :
extension MyProtocol {
// default implementation of doSomething() -> Bool
// conforming types will use this implementation if they don't define their own
func doSomething() -> Bool {
print("do something!")
return true
}
}
Ein Protokoll kann als eine Art verwendet werden, sofern sie nicht haben associatedtype
Anforderungen :
func doStuff(object: MyProtocol) {
// All of MyProtocol's requirements are available on the object
print(object.message)
print(object.doSomething())
}
let items : [MyProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
Sie können auch einen abstrakten Typ definieren, der mehreren Protokollen entspricht:
Mit Swift 3 oder besser wird dies durch Trennen der Protokollliste mit einem Et-Zeichen ( &
) erreicht:
func doStuff(object: MyProtocol & AnotherProtocol) {
// ...
}
let items : [MyProtocol & AnotherProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
Ältere Versionen haben das Syntaxprotokoll protocol<...>
wobei die Protokolle eine durch Kommas getrennte Liste zwischen den spitzen Klammern <>
.
protocol AnotherProtocol {
func doSomethingElse()
}
func doStuff(object: protocol<MyProtocol, AnotherProtocol>) {
// All of MyProtocol & AnotherProtocol's requirements are available on the object
print(object.message)
object.doSomethingElse()
}
// MyStruct, MyClass & MyEnum must now conform to both MyProtocol & AnotherProtocol
let items : [protocol<MyProtocol, AnotherProtocol>] = [MyStruct(), MyClass(), MyEnum.caseA]
Bestehende Typen können erweitert werden , um einem Protokoll zu entsprechen:
extension String : MyProtocol {
// Implement any requirements which String doesn't already satisfy
}
Zugehörige Typanforderungen
Protokolle können zugehörigen Typanforderungen unter Verwendung der Definition associatedtype
Stichwort:
protocol Container {
associatedtype Element
var count: Int { get }
subscript(index: Int) -> Element { get set }
}
Protokolle mit zugehörigen Typanforderungen können nur als generische Einschränkungen verwendet werden :
// These are NOT allowed, because Container has associated type requirements:
func displayValues(container: Container) { ... }
class MyClass { let container: Container }
// > error: protocol 'Container' can only be used as a generic constraint
// > because it has Self or associated type requirements
// These are allowed:
func displayValues<T: Container>(container: T) { ... }
class MyClass<T: Container> { let container: T }
Ein Typ, der das Protokoll entspricht , kann eine zufrieden associatedtype
Anforderung implizit von einer gegebenen Art zu schaffen , in denen das Protokoll der erwartet associatedtype
zu erscheinen:
struct ContainerOfOne<T>: Container {
let count = 1 // satisfy the count requirement
var value: T
// satisfy the subscript associatedtype requirement implicitly,
// by defining the subscript assignment/return type as T
// therefore Swift will infer that T == Element
subscript(index: Int) -> T {
get {
precondition(index == 0)
return value
}
set {
precondition(index == 0)
value = newValue
}
}
}
let container = ContainerOfOne(value: "Hello")
(Beachten Sie, dass Klarheit in diesem Beispiel hinzuzufügen, die generische Platzhalter Typ genannt wird T
- ein passenderer Name wäre Element
, das dem Protokoll des Schatten würde associatedtype Element
. Der Compiler wird immer noch daraus schließen , dass die generische Platzhalter Element
verwendet wird , um die zu erfüllen associatedtype Element
.)
Ein associatedtype
typealias
kann auch explizit durch die Verwendung eines typealias
:
struct ContainerOfOne<T>: Container {
typealias Element = T
subscript(index: Int) -> Element { ... }
// ...
}
Gleiches gilt für Erweiterungen:
// Expose an 8-bit integer as a collection of boolean values (one for each bit).
extension UInt8: Container {
// as noted above, this typealias can be inferred
typealias Element = Bool
var count: Int { return 8 }
subscript(index: Int) -> Bool {
get {
precondition(0 <= index && index < 8)
return self & 1 << UInt8(index) != 0
}
set {
precondition(0 <= index && index < 8)
if newValue {
self |= 1 << UInt8(index)
} else {
self &= ~(1 << UInt8(index))
}
}
}
}
Wenn der konforme Typ die Anforderung bereits erfüllt, ist keine Implementierung erforderlich:
extension Array: Container {} // Array satisfies all requirements, including Element
Muster delegieren
Ein Delegat ist ein allgemeines Entwurfsmuster, das in Cocoa- und CocoaTouch-Frameworks verwendet wird, wobei eine Klasse die Verantwortung für die Implementierung einiger Funktionen an eine andere delegiert. Dies folgt einem Prinzip der Trennung von Bedenken, bei dem die Framework-Klasse generische Funktionalität implementiert, während eine separate Delegateninstanz den spezifischen Anwendungsfall implementiert.
Eine weitere Möglichkeit, das Delegatemuster zu untersuchen, besteht in der Objektkommunikation. Objects
häufig miteinander sprechen, und um dies zu tun, muss ein Objekt einem protocol
entsprechen, um Delegierter eines anderen Objekts zu werden. Sobald diese Einrichtung abgeschlossen ist, spricht das andere Objekt mit seinen Delegierten, wenn interessante Dinge passieren.
Eine Ansicht in der Benutzeroberfläche zum Anzeigen einer Liste von Daten sollte beispielsweise nur für die Logik der Anzeige von Daten verantwortlich sein, nicht für die Entscheidung, welche Daten angezeigt werden sollen.
Lassen Sie uns in ein konkreteres Beispiel eintauchen. wenn Sie zwei Klassen haben, ein Elternteil und ein Kind:
class Parent { }
class Child { }
Und Sie möchten das Elternteil über eine Änderung vom Kind benachrichtigen.
In Swift werden Delegaten mithilfe einer protocol
implementiert. delegate
werden wir ein protocol
deklarieren protocol
das der delegate
implementieren wird. Hier ist Delegat das parent
Objekt.
protocol ChildDelegate: class {
func childDidSomething()
}
Das untergeordnete Objekt muss eine Eigenschaft angeben, um den Verweis auf den Delegaten zu speichern:
class Child {
weak var delegate: ChildDelegate?
}
Beachten Sie die Variable delegate
ist ein optionales und das Protokoll ChildDelegate
markiert nur durch Klassentyp umgesetzt werden (ohne dass dies die delegate
Variable nicht als deklariert werden können weak
Referenz Vermeidung jeder Zyklus beibehalten. Das bedeutet , dass , wenn der delegate
Variable ist nicht mehr woanders referenziert, wird es veröffentlicht). Dies bedeutet, dass die übergeordnete Klasse den Delegaten nur dann registriert, wenn er benötigt wird und verfügbar ist.
Um unseren Delegierten als weak
zu kennzeichnen, müssen wir unser ChildDelegate-Protokoll auf Referenztypen beschränken, indem Sie in der Protokolldeklaration das Schlüsselwort class
hinzufügen.
Wenn das Kind in diesem Beispiel etwas tut und sein Elternteil benachrichtigen muss, ruft das Kind Folgendes an:
delegate?.childDidSomething()
Wenn der Stellvertreter definiert wurde, wird der Stellvertreter darüber informiert, dass das Kind etwas unternommen hat.
Die übergeordnete Klasse muss das ChildDelegate
Protokoll erweitern, um auf ihre Aktionen reagieren zu können. Dies kann direkt in der übergeordneten Klasse erfolgen:
class Parent: ChildDelegate {
...
func childDidSomething() {
print("Yay!")
}
}
Oder mit einer Erweiterung:
extension Parent: ChildDelegate {
func childDidSomething() {
print("Yay!")
}
}
Der Elternteil muss dem Kind auch mitteilen, dass es der Delegierte des Kindes ist:
// In the parent
let child = Child()
child.delegate = self
Standardmäßig erlaubt ein Swift- protocol
nicht die Implementierung einer optionalen Funktion. Diese können nur angegeben werden, wenn Ihr Protokoll mit dem Attribut @objc
und dem optional
Modifikator markiert ist.
Beispielsweise implementiert UITableView
das generische Verhalten einer Tabellensicht in iOS. Der Benutzer muss jedoch zwei Delegat-Klassen namens UITableViewDelegate
und UITableViewDataSource
implementieren, die das UITableViewDelegate
und das Verhalten der jeweiligen Zellen implementieren.
@objc public protocol UITableViewDelegate : NSObjectProtocol, UIScrollViewDelegate { // Display customization optional public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) optional public func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) optional public func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) optional public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) ... }
Sie können dieses Protokoll implementieren, indem Sie Ihre Klassendefinition ändern, zum Beispiel:
class MyViewController : UIViewController, UITableViewDelegate
Alle Methoden, die in der Protokolldefinition nicht als optional
gekennzeichnet sind (in diesem Fall UITableViewDelegate
), müssen implementiert werden.
Protokollerweiterung für eine bestimmte konforme Klasse
Sie können die Standardprotokollimplementierung für eine bestimmte Klasse schreiben.
protocol MyProtocol {
func doSomething()
}
extension MyProtocol where Self: UIViewController {
func doSomething() {
print("UIViewController default protocol implementation")
}
}
class MyViewController: UIViewController, MyProtocol { }
let vc = MyViewController()
vc.doSomething() // Prints "UIViewController default protocol implementation"
Verwenden des RawRepresentable-Protokolls (Extensible Enum)
// RawRepresentable has an associatedType RawValue.
// For this struct, we will make the compiler infer the type
// by implementing the rawValue variable with a type of String
//
// Compiler infers RawValue = String without needing typealias
//
struct NotificationName: RawRepresentable {
let rawValue: String
static let dataFinished = NotificationNames(rawValue: "DataFinishedNotification")
}
Diese Struktur kann an anderer Stelle erweitert werden, um Fälle hinzuzufügen
extension NotificationName {
static let documentationLaunched = NotificationNames(rawValue: "DocumentationLaunchedNotification")
}
Eine Schnittstelle kann um jeden RawRepresentable-Typ oder speziell um Ihre Enumerationsstruktur herum entworfen werden
func post(notification notification: NotificationName) -> Void {
// use notification.rawValue
}
Auf der Anrufseite können Sie die Punktsyntaxkürzel für den typsicheren NotificationName verwenden
post(notification: .dataFinished)
Verwenden der generischen RawRepresentable-Funktion
// RawRepresentable has an associate type, so the
// method that wants to accept any type conforming to
// RawRepresentable needs to be generic
func observe<T: RawRepresentable>(object: T) -> Void {
// object.rawValue
}
Protokolle nur für Klassen
Ein Protokoll kann angeben, dass nur eine Klasse es mithilfe des Schlüsselworts class
in seiner Vererbungsliste implementieren kann. Dieses Schlüsselwort muss vor allen anderen geerbten Protokollen in dieser Liste stehen.
protocol ClassOnlyProtocol: class, SomeOtherProtocol {
// Protocol requirements
}
Wenn ein Nicht-Klassentyp versucht, ClassOnlyProtocol
zu implementieren, wird ein Compiler-Fehler generiert.
struct MyStruct: ClassOnlyProtocol {
// error: Non-class type 'MyStruct' cannot conform to class protocol 'ClassOnlyProtocol'
}
Andere Protokolle können vom ClassOnlyProtocol
erben, sie haben jedoch dieselbe Klassenanforderung.
protocol MyProtocol: ClassOnlyProtocol {
// ClassOnlyProtocol Requirements
// MyProtocol Requirements
}
class MySecondClass: MyProtocol {
// ClassOnlyProtocol Requirements
// MyProtocol Requirements
}
Referenzsemantik von Nur-Klassen-Protokollen
Die Verwendung eines Nur-Klassen-Protokolls ermöglicht eine Referenzsemantik, wenn der konforme Typ unbekannt ist.
protocol Foo : class {
var bar : String { get set }
}
func takesAFoo(foo:Foo) {
// this assignment requires reference semantics,
// as foo is a let constant in this scope.
foo.bar = "new value"
}
Foo
es sich bei Foo
um ein Protokoll nur für die Klasse handelt, ist die Zuweisung zu bar
gültig, da der Compiler weiß, dass es sich bei foo
um einen Klassentyp handelt, und daher über eine Referenzsemantik verfügt.
Wenn Foo
kein Klasse-only - Protokoll, würde ein Compiler - Fehler ergibt - wie der konforme Typ sein , könnte Werttyp , der eine erfordern würde var
Annotation um wandelbar zu sein.
protocol Foo {
var bar : String { get set }
}
func takesAFoo(foo:Foo) {
foo.bar = "new value" // error: Cannot assign to property: 'foo' is a 'let' constant
}
func takesAFoo(foo:Foo) {
var foo = foo // mutable copy of foo
foo.bar = "new value" // no error – satisfies both reference and value semantics
}
Schwache Variablen des Protokolltyps
Wenn Sie den Modifikator weak
auf eine Variable des Protokolltyps anwenden, muss dieser Protokolltyp nur für die Klasse gelten, da der weak
nur auf Referenztypen angewendet werden kann.
weak var weakReference : ClassOnlyProtocol?
Hash-fähiges Protokoll implementieren
Typen, die in Sets
und Dictionaries(key)
müssen dem Hashable
Protokoll entsprechen, das vom Equatable
Protokoll erbt.
Benutzerdefinierter Typ, der dem Hashable
Protokoll entspricht, muss implementieren
- Eine berechnete Eigenschaft
hashValue
- Definieren Sie einen der Gleichheitsoperatoren, dh
==
oder!=
.
Folgendes Beispiel implementiert das Hashable
Protokoll für eine benutzerdefinierte struct
:
struct Cell {
var row: Int
var col: Int
init(_ row: Int, _ col: Int) {
self.row = row
self.col = col
}
}
extension Cell: Hashable {
// Satisfy Hashable requirement
var hashValue: Int {
get {
return row.hashValue^col.hashValue
}
}
// Satisfy Equatable requirement
static func ==(lhs: Cell, rhs: Cell) -> Bool {
return lhs.col == rhs.col && lhs.row == rhs.row
}
}
// Now we can make Cell as key of dictonary
var dict = [Cell : String]()
dict[Cell(0, 0)] = "0, 0"
dict[Cell(1, 0)] = "1, 0"
dict[Cell(0, 1)] = "0, 1"
// Also we can create Set of Cells
var set = Set<Cell>()
set.insert(Cell(0, 0))
set.insert(Cell(1, 0))
Hinweis : Es ist nicht erforderlich, dass verschiedene Werte im benutzerdefinierten Typ unterschiedliche Hashwerte haben. Kollisionen sind zulässig. Wenn die Hashwerte gleich sind, wird der Gleichheitsoperator verwendet, um die tatsächliche Gleichheit zu bestimmen.