Sök…


Introduktion

Protokoll är ett sätt att specificera hur man använder ett objekt. De beskriver en uppsättning egenskaper och metoder som en klass, struktur eller enum bör tillhandahålla, även om protokoll inte innehåller några begränsningar för genomförandet.

Anmärkningar

Ett Swift-protokoll är en samling krav som överensstämmande typer måste implementera. Protokollet kan sedan användas på de flesta platser där en typ förväntas, till exempel Arrays och generiska krav.

Protokollmedlemmar delar alltid samma åtkomstkvalificering som hela protokollet och kan inte specificeras separat. Även om ett protokoll kan begränsa åtkomst med krav på getter eller setter, enligt exemplen ovan.

Mer information om protokoll finns i The Swift Programming Language .

Objektiv-C-protokoll liknar Swift-protokoll.

Protokoll är också jämförbara med Java-gränssnitt .

Grunder om protokoll

Om protokoll

Ett protokoll anger initialiserare, egenskaper, funktioner, prenumerationer och tillhörande typer som krävs av en Swift-objekttyp (klass, struktur eller enum) som överensstämmer med protokollet. På vissa språk är liknande idéer för kravspecifikationer för efterföljande objekt kända som "gränssnitt".

Ett deklarerat och definierat protokoll är en typ, i sig själv, med en signatur av de angivna kraven, något liknande det sätt på vilket Swift-funktioner är en typ baserat på deras signatur av parametrar och returer.

Swift Protocol-specifikationer kan vara valfria, uttryckligen krävda och / eller ges standardimplementeringar via en anläggning som kallas Protocol Extensions. En snabb objekttyp (klass, struktur eller enum) som vill överensstämma med ett protokoll som är utsläppt med tillägg för alla dess specificerade krav behöver endast ange sin önskan att uppfylla för att vara i full överensstämmelse. Standardimplementeringsfaciliteten för protokolltillägg kan räcka för att uppfylla alla skyldigheter att följa ett protokoll.

Protokoll kan ärvas av andra protokoll. Detta, i samband med protokollförlängningar, innebär att protokoll kan och bör ses som ett viktigt inslag i Swift.

Protokoll och förlängningar är viktiga för att realisera Swifts bredare mål och strategier för att utforma flexibilitet och utvecklingsprocesser. Det huvudsakliga angivna syftet med Swifts protokoll och förlängningsförmåga är att underlätta kompositionsdesign i programarkitektur och utveckling. Detta kallas protokollorienterad programmering. Krossiga gamla tidtagare anser att detta är överlägset med fokus på OOP-design.

Protokoll definierar gränssnitt som kan implementeras av valfri struktur , klass eller 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
}

Egenskaper som definieras i protokoll måste antingen antecknas som { get } eller { get set } . { get } innebär att egenskapen måste vara gettable, och därför kan den implementeras som alla typer av fastigheter. { get set } innebär att egenskapen måste vara inställbar såväl som möjlig att fastställa.

En struktur, klass eller enum kan överensstämma med ett protokoll:

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
}

Ett protokoll kan också definiera en standardimplementering för något av dess krav genom en tillägg :

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

Ett protokoll kan användas som en typ , förutsatt att det inte har associatedtype typkrav :

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]

Du kan också definiera en abstrakt typ som överensstämmer med flera protokoll:

3,0

Med Swift 3 eller bättre görs detta genom att separera listan över protokoll med en ampersand ( & ):

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

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

Äldre versioner har syntaxprotokoll protocol<...> där protokollen är en kommaseparerad lista mellan vinkelfästena <> .

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]

Befintliga typer kan utvidgas till att överensstämma med ett protokoll:

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

Tillhörande typkrav

Protokoll kan definiera tillhörande typkrav med hjälp av associatedtype typsökord:

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

Protokoll med tillhörande typkrav kan endast användas som generiska begränsningar :

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

En typ som överensstämmer med protokollet kan tillfredsställa ett krav på associatedtype typ implicit genom att tillhandahålla en given typ där protokollet förväntar sig att associatedtype typ ska visas:

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

(Observera att för att lägga till klarhet för att detta exempel är den generiska platshållare typ som heter T - ett mer passande namn skulle vara Element , som skulle skugga protokollets associatedtype Element . Kompilatorn kommer fortfarande dra slutsatsen att den generiska platshållare Element används för att tillfredsställa associatedtype Element .)

En associatedtype typealias kan också tillfredsställas uttryckligen genom användning av en typealias :

struct ContainerOfOne<T>: Container {

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

    // ...
}

Detsamma gäller för tillägg:

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

Om den överensstämmande typen redan uppfyller kravet behövs ingen implementering:

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

Delegerat mönster

En delegat är ett vanligt designmönster som används i ramarna för Cocoa och CocoaTouch, där en klass delegerar ansvaret för att implementera viss funktionalitet till en annan. Detta följer en princip om separering av oro, där ramklassen implementerar generisk funktionalitet medan en separat delegatinstans implementerar det specifika användningsfallet.

Ett annat sätt att undersöka delegatmönster är när det gäller objektkommunikation. Objects behöver ofta prata med varandra och för att göra det måste ett objekt överensstämma med ett protocol för att bli delegat för ett annat objekt. När denna installation har gjorts talar det andra objektet tillbaka till sina delegater när intressanta saker händer.

Exempelvis bör en vy i användargränssnittet för att visa en lista med data endast vara ansvarig för logiken för hur data visas, inte för att bestämma vilken data som ska visas.

Låt oss dyka in i ett mer konkret exempel. om du har två klasser, en förälder och ett barn:

class Parent { }
class Child { }

Och du vill meddela föräldern om en förändring från barnet.

I Swift implementeras delegater med hjälp av en protocol och så kommer vi att förklara ett protocol som delegate kommer att genomföra. Här är delegaten parent .

protocol ChildDelegate: class {
    func childDidSomething()
}

Barnet måste förklara en egendom för att lagra referensen till delegaten:

class Child {
    weak var delegate: ChildDelegate?
}

Observera att den variabla delegate är ett valfritt och protokollet ChildDelegate markeras för att endast implementeras efter klasstyp (utan detta kan delegate inte förklaras som en weak referens för att undvika någon behållningscykel. Detta betyder att om delegate inte längre är längre refereras någon annanstans, kommer det att släppas). Detta är så att förälderklassen bara registrerar delegaten när den behövs och är tillgänglig.

Också för att markera vår delegat som weak måste vi begränsa vår ChildDelegate protokoll till referenstyper genom att lägga till class sökord i protokolldeklaration.

I det här exemplet, när barnet gör något och måste meddela sin förälder, kommer barnet att ringa:

delegate?.childDidSomething()

Om delegaten har definierats meddelas delegaten att barnet har gjort något.

Förälderklassen måste utöka ChildDelegate protokollet för att kunna svara på dess handlingar. Detta kan göras direkt på förälderklassen:

class Parent: ChildDelegate {
    ...

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

Eller använda ett tillägg:

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

Föräldern måste också berätta för barnet att det är barnets delegat:

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

Som standard tillåter inte ett Swift- protocol en valfri funktion att implementeras. Dessa kan endast specificeras om ditt protokoll är markerat med @objc attributet och den optional modifieraren.

Till exempel implementerar UITableView det generiska beteendet för en tabellvy i iOS, men användaren måste implementera två UITableViewDelegate kallas UITableViewDelegate och UITableViewDataSource som implementerar hur de specifika cellerna ser ut och uppför sig.

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

Du kan implementera detta protokoll genom att ändra din klassdefinition, till exempel:

class MyViewController : UIViewController, UITableViewDelegate

Alla metoder som inte är markerade som optional i protokolldefinitionen ( UITableViewDelegate i detta fall) måste implementeras.

Protokollförlängning för en specifik överensstämmande klass

Du kan skriva standardprotokollimplementeringen för en viss klass.

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"

Använda RawRepresentable-protokollet (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")
}

Denna struktur kan utvidgas någon annanstans för att lägga till fall

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

Och ett gränssnitt kan utforma runt alla typer av RawRepresentable eller specifikt din enumstruktur

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

På samtalssidan kan du använda punktsyntax-kort för typsäker meddelandenamn

post(notification: .dataFinished)

Använda generisk 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
}

Protokoll för endast klass

Ett protokoll kan ange att endast en klass kan genomföra det genom att använda class sökord i sin arvslistan. Detta nyckelord måste visas före andra ärvda protokoll i denna lista.

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

Om en icke- ClassOnlyProtocol försöker implementera ClassOnlyProtocol kommer ett kompilatorfel att genereras.

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

Andra protokoll kan ärva från ClassOnlyProtocol , men de kommer att ha samma klasskrav.

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

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

Referenssemantik för endast klassprotokoll

Om du använder ett klassprotokoll kan endast referenssemantik användas när den överensstämmande typen är okänd.

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

I det här exemplet, eftersom Foo är ett klassprotokoll, är tilldelningen till bar giltig eftersom kompilatorn vet att foo är en klasstyp och därför har referenssemantik.

Om Foo inte var en klass endast protokoll skulle en kompilator felet gav - som överensstämmer typ kan vara ett värde typ , vilket skulle kräva en var anteckning för att vara föränderlig.

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
}

Svaga variabler av protokolltyp

När du använder den weak modifieraren på en variabel av protokolltyp måste den protokolltypen endast vara klass, eftersom weak endast kan tillämpas på referenstyper.

weak var weakReference : ClassOnlyProtocol?

Implementera Hashable-protokoll

Typer som används i Sets och Dictionaries(key) måste överensstämma med Hashable protokollet som ärver från Equatable protokollet.

Anpassad typ som Hashable protokollet måste implementeras

  • En beräknad fastighet hashValue
  • Definiera en av jämställdhetsoperatörerna, dvs == eller != .

Följande exempel implementerar Hashable protokollet för en anpassad 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))

Obs : Det är inte nödvändigt att olika värden i anpassad typ har olika hashvärden, kollisioner är acceptabla. Om hashvärden är lika kommer jämställdhetsoperatören att användas för att bestämma verklig jämlikhet.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow