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:

3,0

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]
3,0

Ä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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow