Zoeken…


Invoering

Protocollen zijn een manier om aan te geven hoe een object moet worden gebruikt. Ze beschrijven een set eigenschappen en methoden die een klasse, structuur of opsomming moet bieden, hoewel protocollen geen beperkingen opleggen aan de implementatie.

Opmerkingen

Een Swift-protocol is een verzameling vereisten die conforme typen moeten implementeren. Het protocol kan vervolgens worden gebruikt op de meeste plaatsen waar een type wordt verwacht, bijvoorbeeld arrays en generieke vereisten.

Protocolleden delen altijd dezelfde toegangskwalificatie als het hele protocol en kunnen niet afzonderlijk worden opgegeven. Hoewel een protocol de toegang kan beperken met de vereisten van getter of setter, zoals in bovenstaande voorbeelden.

Zie De snelle programmeertaal voor meer informatie over protocollen.

Objective-C-protocollen zijn vergelijkbaar met Swift-protocollen.

Protocollen zijn ook vergelijkbaar met Java-interfaces .

Basisprincipes van het protocol

Over protocollen

Een protocol specificeert initializers, eigenschappen, functies, subscripts en bijbehorende types die vereist zijn voor een Swift-objecttype (klasse, struct of enum) conform het protocol. In sommige talen worden vergelijkbare ideeën voor vereistenpecificaties van volgende objecten 'interfaces' genoemd.

Een verklaard en gedefinieerd protocol is op zichzelf een type met een handtekening van de gestelde eisen, enigszins vergelijkbaar met de manier waarop Swift-functies een type zijn op basis van hun handtekening van parameters en retouren.

Swift-protocolspecificaties kunnen optioneel, expliciet vereist en / of standaardimplementaties zijn via een faciliteit die bekend staat als protocolextensies. Een Swift-objecttype (klasse, struct of enum) die zich wenst te conformeren aan een protocol dat is ingevuld met extensies voor al zijn gespecificeerde vereisten, behoeft alleen zijn wens te conformeren om volledig conform te zijn. De standaard implementatiefaciliteit van Protocol Extensies kan voldoende zijn om te voldoen aan alle verplichtingen om aan een Protocol te voldoen.

Protocollen kunnen worden overgenomen door andere protocollen. Dit betekent, in combinatie met protocoluitbreidingen, dat protocollen als een belangrijk kenmerk van Swift kunnen en moeten worden beschouwd.

Protocollen en uitbreidingen zijn belangrijk om de bredere doelstellingen en benaderingen van Swift voor programmaflexibiliteit en ontwikkelingsprocessen te realiseren. Het primaire verklaarde doel van Swift's protocol- en uitbreidingsmogelijkheden is het faciliteren van compositorisch ontwerp in programma-architectuur en ontwikkeling. Dit wordt Protocol Oriented Programming genoemd. Knapperige oldtimers beschouwen deze superieur als een focus op OOP-ontwerp.

Protocollen definiëren interfaces die kunnen worden geïmplementeerd door elke struct , klasse of enum :

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
}

Eigenschappen die in protocollen zijn gedefinieerd, moeten worden geannoteerd als { get } of { get set } . { get } betekent dat de eigenschap gettable moet zijn en daarom als elke soort eigenschap kan worden geïmplementeerd. { get set } betekent dat de eigenschap zowel instelbaar als gettable moet zijn.

Een struct, klasse of opsomming kan voldoen aan een protocol:

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
}

Een protocol kan ook een standaardimplementatie definiëren voor elk van de vereisten via een extensie :

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
    }
}

Een protocol kan als een type worden gebruikt , op voorwaarde dat het geen associatedtype typevereisten heeft :

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]

U kunt ook een abstract type definiëren dat voldoet aan meerdere protocollen:

3.0

Met Swift 3 of hoger wordt dit gedaan door de lijst met protocollen te scheiden met een en-teken ( & ):

func doStuff(object: MyProtocol & AnotherProtocol) {
    // ...
}

let items : [MyProtocol & AnotherProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
3.0

Oudere versies hebben een syntaxisprotocol protocol<...> waarbij de protocollen een door komma's gescheiden lijst zijn tussen de punthaken <> .

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]

Bestaande typen kunnen worden uitgebreid om te voldoen aan een protocol:

extension String : MyProtocol {
    // Implement any requirements which String doesn't already satisfy
}

Bijbehorende typevereisten

Protocollen kunnen bijbehorende typevereisten definiëren met behulp van het associatedtype trefwoord:

protocol Container {
    associatedtype Element
    var count: Int { get }
    subscript(index: Int) -> Element { get set }
}

Protocollen met bijbehorende typevereisten kunnen alleen worden gebruikt als generieke beperkingen :

// 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 }

Een type dat overeenkomt met het protocol kan een aan associatedtype vereiste impliciet, door een bepaald type indien het protocol verwacht dat de associatedtype worden weergegeven:

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")

(Merk op dat voor de duidelijkheid van dit voorbeeld het generieke type tijdelijke aanduiding de naam T - een geschiktere naam zou Element , dat het associatedtype Element het protocol zou associatedtype Element . De compiler concludeert nog steeds dat het generieke Element tijdelijke aanduiding wordt gebruikt om aan het associatedtype Element te voldoen associatedtype Element .)

Aan een associatedtype kan ook expliciet worden voldaan door het gebruik van een typealias :

struct ContainerOfOne<T>: Container {

    typealias Element = T
    subscript(index: Int) -> Element { ... }

    // ...
}

Hetzelfde geldt voor extensies:

// 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))
            }
        }
    }
}

Als het conformerende type al aan de vereiste voldoet, is geen implementatie vereist:

extension Array: Container {}  // Array satisfies all requirements, including Element

Patroon delegeren

Een gemachtigde is een gebruikelijk ontwerppatroon dat wordt gebruikt in Cocoa en CocoaTouch-frameworks, waarbij de ene klasse de verantwoordelijkheid voor het implementeren van bepaalde functionaliteit aan de andere delegeert. Dit volgt een principe van scheiding van punten van zorg, waarbij de framework-klasse generieke functionaliteit implementeert, terwijl een afzonderlijke gedelegeerde instantie de specifieke use case implementeert.

Een andere manier om het deelnemerspatroon te bekijken is in termen van objectcommunicatie. Objects vaak met elkaar praten en daarvoor moet een object voldoen aan een protocol om een afgevaardigde van een ander object te worden. Zodra deze installatie is voltooid, praat het andere object terug naar zijn afgevaardigden wanneer er interessante dingen gebeuren.

Een weergave in een gebruikersinterface om een lijst met gegevens weer te geven, moet bijvoorbeeld alleen verantwoordelijk zijn voor de logica van hoe gegevens worden weergegeven, niet voor de beslissing welke gegevens moeten worden weergegeven.

Laten we een meer concreet voorbeeld bekijken. als je twee klassen hebt, een ouder en een kind:

class Parent { }
class Child { }

En u wilt de ouder op de hoogte stellen van een wijziging van het kind.

In Swift worden afgevaardigden geïmplementeerd met behulp van een protocol en daarom verklaren we een protocol dat de delegate zal implementeren. Hier is de gemachtigde het parent object.

protocol ChildDelegate: class {
    func childDidSomething()
}

Het kind moet een eigenschap aangeven om de verwijzing naar de gemachtigde op te slaan:

class Child {
    weak var delegate: ChildDelegate?
}

Merk op dat de variabele delegate een optioneel is en dat het protocol ChildDelegate is gemarkeerd om alleen per ChildDelegate te worden geïmplementeerd (zonder dit kan de delegate variabele niet worden gedeclareerd als een weak referentie om een ChildDelegate te voorkomen. Dit betekent dat als de delegate variabele niet langer is waarnaar elders wordt verwezen, wordt deze vrijgegeven). Dit betekent dat de bovenliggende klasse de gedelegeerde alleen registreert wanneer deze nodig en beschikbaar is.

Ook om onze gemachtigde als weak te markeren, moeten we ons ChildDelegate-protocol beperken tot referentietypes door class sleutelwoorden toe te voegen in de protocoldeclaratie.

Wanneer het kind in dit voorbeeld iets doet en zijn ouder op de hoogte moet stellen, belt het kind:

delegate?.childDidSomething()

Als de gemachtigde is gedefinieerd, krijgt de gemachtigde een melding dat het kind iets heeft gedaan.

De bovenliggende klasse moet het ChildDelegate protocol uitbreiden om op zijn acties te kunnen reageren. Dit kan rechtstreeks op de bovenliggende klasse worden gedaan:

class Parent: ChildDelegate {
    ...

    func childDidSomething() {
        print("Yay!")
    }
}

Of gebruik een extensie:

extension Parent: ChildDelegate {
    func childDidSomething() {
        print("Yay!")
    }
}

De ouder moet het kind ook vertellen dat het de afgevaardigde van het kind is:

// In the parent
let child = Child()
child.delegate = self

Standaard staat een Swift- protocol niet toe dat een optionele functie wordt geïmplementeerd. Deze kunnen alleen worden opgegeven als uw protocol is gemarkeerd met het kenmerk @objc en de optional modifier.

UITableView implementeert bijvoorbeeld het generieke gedrag van een tabelweergave in iOS, maar de gebruiker moet twee gedelegeerde klassen implementeren, UITableViewDelegate en UITableViewDataSource die implementeren hoe de specifieke cellen eruit zien en zich gedragen.

@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)
        ...
}

U kunt dit protocol implementeren door uw klassendefinitie te wijzigen, bijvoorbeeld:

class MyViewController : UIViewController, UITableViewDelegate

Alle methoden die niet als optional gemarkeerd in de protocoldefinitie ( UITableViewDelegate in dit geval) moeten worden geïmplementeerd.

Protocoluitbreiding voor een specifieke conformerende klasse

U kunt de standaardprotocolimplementatie voor een specifieke klasse schrijven.

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"

Het RawRepresentable-protocol gebruiken (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")
}

Deze structuur kan elders worden uitgebreid om cases toe te voegen

extension NotificationName {
    static let documentationLaunched = NotificationNames(rawValue: "DocumentationLaunchedNotification")
}

En een interface kan rond elk type RawRepresentable of specifiek uw enum struct ontwerpen

func post(notification notification: NotificationName) -> Void {
    // use notification.rawValue
}

Op de oproepsite kunt u puntsyntaxis steno gebruiken voor de typenveilige notificatienaam

post(notification: .dataFinished)

Gebruik van de generieke RawRepresentable-functie

// 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
}

Alleen-klasse protocollen

Een protocol kan specificeren dat alleen een klasse het kan implementeren door het sleutelwoord class in zijn overnamelijst te gebruiken. Dit trefwoord moet vóór alle andere overgenomen protocollen in deze lijst verschijnen.

protocol ClassOnlyProtocol: class, SomeOtherProtocol {
    // Protocol requirements 
}

Als een niet-klasse type ClassOnlyProtocol probeert te implementeren, wordt een compilerfout gegenereerd.

struct MyStruct: ClassOnlyProtocol { 
    // error: Non-class type 'MyStruct' cannot conform to class protocol 'ClassOnlyProtocol'  
}

Andere protocollen kunnen erven van het ClassOnlyProtocol , maar ze hebben dezelfde vereiste voor alleen klassen.

protocol MyProtocol: ClassOnlyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

class MySecondClass: MyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

Referentie semantiek van alleen-klasse protocollen

Het gebruik van een alleen-klasse protocol maakt referentie-semantiek mogelijk wanneer het conformerende type onbekend is.

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"
}

In dit voorbeeld, omdat Foo een protocol is dat alleen voor klassen is, is de toewijzing aan bar geldig omdat de compiler weet dat foo een klasse-type is en daarom referentie-semantiek heeft.

Als Foo geen protocol voor alleen klassen was, zou een compilerfout worden verkregen - omdat het conformerende type een waardetype zou kunnen zijn, waarvoor een var annotatie nodig zou zijn om veranderlijk te zijn.

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
}

Zwakke variabelen van het protocoltype

Wanneer de weak modifier wordt toegepast op een variabele van het protocoltype, moet dat protocoltype alleen klasse zijn, omdat weak alleen kan worden toegepast op referentietypen.

weak var weakReference : ClassOnlyProtocol?

Hashable-protocol implementeren

Typen die worden gebruikt in Sets en Dictionaries(key) moeten voldoen aan het Hashable protocol dat Equatable van het Equatable protocol.

Aangepast type dat voldoet aan Hashable protocol moet worden geïmplementeerd

  • Een berekende eigenschap hashValue
  • Definieer een van de gelijkheidsexploitanten ie == of != .

Het volgende voorbeeld implementeert Hashable protocol voor een aangepaste 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))

Opmerking : het is niet nodig dat verschillende waarden in het aangepaste type verschillende hash-waarden hebben, botsingen zijn acceptabel. Als hash-waarden gelijk zijn, wordt de operator voor gelijkheid gebruikt om de echte gelijkheid te bepalen.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow