Ricerca…


introduzione

I protocolli sono un modo per specificare come utilizzare un oggetto. Descrivono un insieme di proprietà e metodi che una classe, struttura o enum dovrebbero fornire, sebbene i protocolli non pongano restrizioni all'implementazione.

Osservazioni

Un protocollo Swift è una raccolta di requisiti che devono essere implementati dai tipi conformi. Il protocollo può quindi essere utilizzato nella maggior parte dei casi in cui è previsto un tipo, ad esempio matrici e requisiti generici.

I membri del protocollo condividono sempre lo stesso qualificatore di accesso dell'intero protocollo e non possono essere specificati separatamente. Sebbene un protocollo possa limitare l'accesso con requisiti getter o setter, come negli esempi sopra.

Per ulteriori informazioni sui protocolli, vedere The Swift Programming Language .

I protocolli Objective-C sono simili ai protocolli Swift.

I protocolli sono anche paragonabili alle interfacce Java .

Nozioni di base sul protocollo

Informazioni sui protocolli

Un protocollo specifica inizializzatori, proprietà, funzioni, pedici e tipi associati richiesti di un tipo di oggetto Swift (classe, struct o enum) conforme al protocollo. In alcune lingue idee simili per le specifiche dei requisiti degli oggetti successivi sono conosciute come "interfacce".

Un Protocollo dichiarato e definito è un Tipo, in sé e per sé, con una firma dei suoi requisiti dichiarati, in qualche modo simile al modo in cui le Funzioni Swift sono un Tipo basato sulla loro firma di parametri e ritorni.

Le specifiche del protocollo Swift possono essere facoltative, richieste esplicitamente e / o fornite implementazioni predefinite tramite una funzione nota come Extension Protocol. Un tipo di oggetto Swift (classe, struct o enum) che desidera conformarsi a un protocollo che è arricchito con estensioni per tutti i suoi requisiti specificati, deve solo indicare il suo desiderio di conformarsi per essere in piena conformità. La funzionalità di implementazione predefinita delle estensioni del protocollo può essere sufficiente per soddisfare tutti gli obblighi di conformità a un protocollo.

I protocolli possono essere ereditati da altri protocolli. Questo, in congiunzione con Protocol Extensions, significa che i protocolli possono e devono essere considerati come una caratteristica significativa di Swift.

I protocolli e le estensioni sono importanti per realizzare gli obiettivi e gli approcci più ampi di Swift alla flessibilità della progettazione del programma e ai processi di sviluppo. Lo scopo principale dichiarato della capacità di protocollo e estensione di Swift è l'agevolazione della progettazione compositiva nell'architettura e nello sviluppo del programma. Si parla di programmazione orientata al protocollo. I vecchi croccanti considerano questo superiore l'attenzione al design OOP.

I protocolli definiscono interfacce che possono essere implementate da qualsiasi struct , classe o 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
}

Le proprietà definite nei protocolli devono essere annotate come { get } o { get set } . { get } significa che la proprietà deve essere disponibile e quindi può essere implementata come qualsiasi tipo di proprietà. { get set } significa che la proprietà deve essere impostabile e ricevibile.

Una struttura, una classe o una enum possono essere conformi a un protocollo:

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
}

Un protocollo può anche definire un'implementazione predefinita per uno qualsiasi dei suoi requisiti attraverso un'estensione :

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

Un protocollo può essere usato come un tipo , a condizione che non abbia i requisiti del tipo associatedtype :

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]

Puoi anche definire un tipo astratto conforme a più protocolli:

3.0

Con Swift 3 o superiore, questo viene fatto separando la lista dei protocolli con una e commerciale ( & ):

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

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

Le versioni precedenti hanno il protocol<...> sintassi protocol<...> dove i protocolli sono un elenco separato da virgole tra parentesi angolari <> .

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]

I tipi esistenti possono essere estesi per conformarsi a un protocollo:

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

Requisiti del tipo associato

I protocolli possono definire i requisiti di tipo associati utilizzando la parola chiave associatedtype :

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

I protocolli con i requisiti di tipo associati possono essere utilizzati solo come vincoli generici :

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

Un tipo conforme al protocollo può soddisfare implicitamente un requisito di tipo associatedtype , fornendo un determinato tipo in cui il protocollo si aspetta che venga visualizzato il tipo associatedtype :

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

(Si noti che per aggiungere chiarezza a questo esempio, il tipo di segnaposto generico è denominato T - un nome più adatto sarebbe Element , che ombreggia l' associatedtype Element del protocollo. Il compilatore dedurrà comunque che l' Element segnaposto generico viene utilizzato per soddisfare il tipo associatedtype Element Requisito associatedtype Element .)

Un tipo associatedtype può anche essere soddisfatto esplicitamente attraverso l'uso di un typealias :

struct ContainerOfOne<T>: Container {

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

    // ...
}

Lo stesso vale per le estensioni:

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

Se il tipo conforme soddisfa già il requisito, non è necessaria alcuna implementazione:

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

Modello delegato

Un delegato è un modello di progettazione comune utilizzato nei framework Cocoa e CocoaTouch, in cui una classe delega la responsabilità di implementare alcune funzionalità a un'altra. Ciò segue un principio di separazione delle preoccupazioni, in cui la classe framework implementa funzionalità generiche mentre un'istanza delegato separata implementa il caso d'uso specifico.

Un altro modo per esaminare il modello delegato è in termini di comunicazione dell'oggetto. Objects spesso bisogno di parlare tra loro e per farlo un oggetto deve essere conforme a un protocol per diventare un delegato di un altro oggetto. Una volta che questa configurazione è stata eseguita, l'altro oggetto torna ai suoi delegati quando accadono cose interessanti.

Ad esempio, una vista nell'interfaccia utente per visualizzare un elenco di dati dovrebbe essere responsabile solo della logica di come vengono visualizzati i dati, non per decidere quali dati devono essere visualizzati.

Facciamo un esempio più concreto. se hai due classi, un genitore e un bambino:

class Parent { }
class Child { }

E vuoi informare il genitore di un cambiamento dal bambino.

In Swift, i delegati vengono implementati usando una dichiarazione di protocol e quindi dichiareremo un protocol che il delegate implementerà. Qui delegato è l'oggetto parent .

protocol ChildDelegate: class {
    func childDidSomething()
}

Il bambino deve dichiarare una proprietà per memorizzare il riferimento al delegato:

class Child {
    weak var delegate: ChildDelegate?
}

Si noti che la variabile delegate è facoltativa e il protocollo ChildDelegate è contrassegnato per essere implementato solo per tipo di classe (senza che la variabile delegate non possa essere dichiarata come riferimento weak evitando qualsiasi ciclo di conservazione. Ciò significa che se la variabile delegate non è più riferito altrove, sarà rilasciato). Questo è così la classe genitore registra solo il delegato quando è necessario e disponibile.

Inoltre, per contrassegnare il nostro delegato come weak , dobbiamo limitare il nostro protocollo ChildDelegate ai tipi di riferimento aggiungendo la parola chiave della class nella dichiarazione del protocollo.

In questo esempio, quando il bambino fa qualcosa e ha bisogno di notificare il suo genitore, il bambino chiamerà:

delegate?.childDidSomething()

Se il delegato è stato definito, al delegato verrà notificato che il bambino ha fatto qualcosa.

La classe genitore dovrà estendere il protocollo ChildDelegate per essere in grado di rispondere alle sue azioni. Questo può essere fatto direttamente sulla classe genitore:

class Parent: ChildDelegate {
    ...

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

O utilizzando un'estensione:

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

Il genitore deve anche dire al bambino che è il delegato del bambino:

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

Di default un protocol Swift non consente l'implementazione di una funzione opzionale. Questi possono essere specificati solo se il tuo protocollo è contrassegnato con l'attributo @objc e il modificatore optional .

Ad esempio, UITableView implementa il comportamento generico di una vista tabella in iOS, ma l'utente deve implementare due classi delegate denominate UITableViewDelegate e UITableViewDataSource che implementano l'aspetto e il comportamento delle celle specifiche.

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

È possibile implementare questo protocollo modificando la definizione della classe, ad esempio:

class MyViewController : UIViewController, UITableViewDelegate

Tutti i metodi non contrassegnati come optional nella definizione del protocollo ( UITableViewDelegate in questo caso) devono essere implementati.

Estensione del protocollo per una classe conforme specifica

È possibile scrivere l' implementazione del protocollo predefinita per una classe specifica.

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"

Utilizzo del protocollo RawRepresentable (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")
}

Questa struttura può essere estesa altrove per aggiungere casi

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

E un'interfaccia può progettare attorno a qualsiasi tipo di RawRepresentable o in particolare alla tua struttura di enum

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

Al sito di chiamata, è possibile utilizzare la sintassi del sintassi del punto per il NotificationName typesafe

post(notification: .dataFinished)

Utilizzo della funzione RawRepresentable generica

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

Protocolli di sola classe

Un protocollo può specificare che solo una classe può implementarla utilizzando la parola chiave class nel suo elenco di eredità. Questa parola chiave deve apparire prima di qualsiasi altro protocollo ereditato in questo elenco.

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

Se un tipo non di classe tenta di implementare ClassOnlyProtocol , verrà generato un errore del compilatore.

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

Altri protocolli possono ereditare da ClassOnlyProtocol , ma avranno lo stesso requisito di sola classe.

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

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

Semantica di riferimento dei protocolli di sola classe

L'uso di un protocollo di solo classe consente la semantica di riferimento quando il tipo conforme è sconosciuto.

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 questo esempio, dato che Foo è un protocollo di sola classe, l'assegnazione alla bar è valida poiché il compilatore sa che foo è un tipo di classe e quindi ha semantica di riferimento.

Se Foo non era un protocollo di sola classe, veniva restituito un errore del compilatore, poiché il tipo conforme poteva essere un tipo di valore , che richiederebbe un'annotazione var per poter essere mutato.

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
}

Variabili deboli del tipo di protocollo

Quando si applica il modificatore weak a una variabile del tipo di protocollo, tale tipo di protocollo deve essere solo di classe, in quanto weak può essere applicato solo ai tipi di riferimento.

weak var weakReference : ClassOnlyProtocol?

Implementazione del protocollo Hashable

I tipi utilizzati in Sets e Dictionaries(key) devono essere conformi al protocollo Hashable che eredita dal protocollo Equatable .

Hashable tipo personalizzato conforme al protocollo Hashable

  • Una proprietà calcolata hashValue
  • Definire uno degli operatori di uguaglianza, ovvero == o != .

L'esempio seguente implementa il protocollo Hashable per una struct personalizzata:

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

Nota : non è necessario che valori diversi nel tipo personalizzato abbiano valori hash diversi, le collisioni sono accettabili. Se i valori di hash sono uguali, verrà utilizzato l'operatore di uguaglianza per determinare l'uguaglianza reale.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow