Recherche…


Introduction

Les protocoles sont un moyen de spécifier comment utiliser un objet. Ils décrivent un ensemble de propriétés et de méthodes qu'une classe, une structure ou un enum devrait fournir, bien que les protocoles ne posent aucune restriction à l'implémentation.

Remarques

Un protocole Swift est un ensemble d'exigences que les types conformes doivent implémenter. Le protocole peut ensuite être utilisé dans la plupart des endroits où un type est attendu, par exemple des tableaux et des exigences génériques.

Les membres du protocole partagent toujours le même qualificateur d'accès que le protocole complet et ne peuvent pas être spécifiés séparément. Bien qu'un protocole puisse restreindre l'accès avec les exigences de getter ou de setter, comme dans les exemples ci-dessus.

Pour plus d'informations sur les protocoles, voir Le langage de programmation Swift .

Les protocoles Objective-C sont similaires aux protocoles Swift.

Les protocoles sont également comparables aux interfaces Java .

Principes de base du protocole

À propos des protocoles

Un protocole spécifie les initialiseurs, les propriétés, les fonctions, les indices et les types associés requis pour un type d'objet Swift (classe, struct ou enum) conforme au protocole. Dans certaines langues, des idées similaires pour les spécifications d'exigences des objets suivants sont appelées «interfaces».

Un protocole déclaré et défini est un Type, en soi, avec une signature de ses exigences déclarées, quelque peu similaire à la manière dont les Fonctions Swift sont un Type basé sur leur signature de paramètres et de retours.

Les spécifications du protocole Swift peuvent être facultatives, explicitement requises et / ou implémentées par défaut via une fonction appelée Extensions de protocole. Un type d'objet Swift (classe, struct ou enum) souhaitant se conformer à un protocole étoffé avec les extensions pour toutes ses exigences spécifiées doit seulement indiquer son désir de se conformer à la conformité totale. La fonctionnalité d'implémentation par défaut des extensions de protocole peut suffire à remplir toutes les obligations de conformité à un protocole.

Les protocoles peuvent être hérités par d'autres protocoles. Ceci, conjointement avec les extensions de protocole, signifie que les protocoles peuvent et doivent être considérés comme une caractéristique importante de Swift.

Les protocoles et les extensions sont importants pour réaliser les objectifs et les approches plus larges de Swift en matière de flexibilité de conception de programme et de processus de développement. Le principal objectif déclaré de la fonctionnalité Protocole et extension de Swift est la facilitation de la conception de la composition dans l'architecture et le développement des programmes. Ceci est appelé programmation orientée protocole. Les anciens croustillants considèrent cela comme supérieur à la conception de la POO.

Les protocoles définissent des interfaces pouvant être implémentées par toute structure , classe ou énumération :

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
}

Les propriétés définies dans les protocoles doivent être annotées sous la forme { get } ou { get set } . { get } signifie que la propriété doit être mémorable et peut donc être implémentée comme n'importe quel type de propriété. { get set } signifie que la propriété doit être paramétrable et inoubliable.

Une structure, une classe ou un enum peut être conforme à un protocole:

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 protocole peut également définir une implémentation par défaut pour l'une de ses exigences via une extension :

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 protocole peut être utilisé en tant que type , à condition qu'il ne soit pas associatedtype exigences de type :

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]

Vous pouvez également définir un type abstrait conforme à plusieurs protocoles:

3.0

Avec Swift 3 ou supérieur, cela se fait en séparant la liste des protocoles avec une esperluette ( & ):

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

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

Les anciennes versions ont un protocol<...> syntaxe protocol<...> où les protocoles sont une liste séparée par des virgules entre les crochets angulaires <> .

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]

Les types existants peuvent être étendus pour se conformer à un protocole:

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

Exigences de type associé

Les protocoles peuvent définir des exigences de type associés à l' aide du associatedtype mot - clé:

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

Les protocoles avec des exigences de type associées ne peuvent être utilisés que comme contraintes génériques :

// 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 type conforme au protocole peut satisfaire implicitement à une exigence de type associatedtype , en fournissant un type donné où le protocole s'attend à ce que le type associatedtype apparaisse:

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

(Notez que pour ajouter de la clarté à cet exemple, le type d'espace réservé générique est nommé T - un nom plus approprié serait l' Element , qui ombre du protocole d' associatedtype Element . Le compilateur déduira encore que l'espace réservé générique Element est utilisé pour satisfaire les associatedtype Element requis.)

Un type associatedtype peut également être satisfait explicitement par l'utilisation d'un typealias :

struct ContainerOfOne<T>: Container {

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

    // ...
}

La même chose vaut pour les extensions:

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

Si le type conforme répond déjà à l'exigence, aucune implémentation n'est requise:

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

Modèle de délégué

Un délégué est un modèle de conception commun utilisé dans les frameworks Cocoa et CocoaTouch, où une classe délègue la responsabilité de l'implémentation de certaines fonctionnalités à une autre. Cela suit un principe de séparation des préoccupations, dans lequel la classe d'infrastructure implémente des fonctionnalités génériques alors qu'une instance de délégué distinct implémente le cas d'utilisation spécifique.

Une autre manière d’examiner le modèle des délégués est la communication par objet. Objects doivent souvent se parler et, pour ce faire, un objet doit être conforme à un protocol pour devenir un délégué d'un autre objet. Une fois cette configuration effectuée, l’autre objet parle à ses délégués lorsque des choses intéressantes se produisent.

Par exemple, une vue dans l'interface utilisateur pour afficher une liste de données ne devrait être responsable que de la logique d'affichage des données et non pas de la détermination des données à afficher.

Plongeons dans un exemple plus concret. si vous avez deux classes, un parent et un enfant:

class Parent { }
class Child { }

Et vous voulez informer le parent d'un changement de l'enfant.

Dans Swift, les délégués sont implémentés en utilisant une déclaration de protocol et nous allons donc déclarer un protocol que le delegate implémentera. Ici, delegate est l'objet parent .

protocol ChildDelegate: class {
    func childDidSomething()
}

L'enfant doit déclarer une propriété pour stocker la référence au délégué:

class Child {
    weak var delegate: ChildDelegate?
}

Notez la variable delegate est en option et le protocole ChildDelegate est marqué à seulement mis en œuvre par type de classe (sans que cela le delegate variable ne peut être déclarée comme une weak référence évitant tout cycle retenir. Cela signifie que si le delegate variable n'est plus référencé ailleurs, il sera publié). La classe parente enregistre uniquement le délégué lorsque cela est nécessaire et disponible.

Aussi, pour marquer notre délégué comme weak nous devons contraindre notre protocole ChildDelegate à référencer les types en ajoutant class mot class clé de class dans la déclaration de protocole.

Dans cet exemple, lorsque l'enfant fait quelque chose et doit notifier son parent, l'enfant appelle:

delegate?.childDidSomething()

Si le délégué a été défini, le délégué sera informé que l'enfant a fait quelque chose.

La classe parente devra étendre le protocole ChildDelegate pour pouvoir répondre à ses actions. Cela peut être fait directement sur la classe parente:

class Parent: ChildDelegate {
    ...

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

Ou en utilisant une extension:

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

Le parent doit également dire à l'enfant que c'est le délégué de l'enfant:

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

Par défaut, un protocol Swift ne permet pas l'implémentation d'une fonction optionnelle. Celles-ci ne peuvent être spécifiées que si votre protocole est marqué avec l'attribut @objc et le modificateur optional .

Par exemple, UITableView implémente le comportement générique d'une vue de table dans iOS, mais l'utilisateur doit implémenter deux classes de délégué appelées UITableViewDelegate et UITableViewDataSource qui implémentent l'apparence et le comportement des cellules spécifiques.

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

Vous pouvez implémenter ce protocole en modifiant votre définition de classe, par exemple:

class MyViewController : UIViewController, UITableViewDelegate

Toutes les méthodes non marquées comme étant optional dans la définition du protocole ( UITableViewDelegate dans ce cas) doivent être implémentées.

Extension de protocole pour une classe conforme spécifique

Vous pouvez écrire l' implémentation de protocole par défaut pour une classe spécifique.

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"

Utilisation du protocole 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")
}

Cette structure peut être étendue ailleurs pour ajouter des cas

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

Et une interface peut concevoir autour de tout type RawRepresentable ou plus précisément de votre enum struct

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

Sur le site d'appel, vous pouvez utiliser un raccourci de syntaxe de point pour les typesafe NotificationName

post(notification: .dataFinished)

Utilisation de la fonction générique RawRepresentable

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

Protocoles de classe uniquement

Un protocole peut spécifier que seule une classe peut l'implémenter en utilisant le mot class clé class dans sa liste d'héritage. Ce mot-clé doit apparaître avant tout autre protocole hérité de cette liste.

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

Si un type non-classe tente d'implémenter ClassOnlyProtocol , une erreur de compilation sera générée.

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

D'autres protocoles peuvent hériter du ClassOnlyProtocol , mais ils auront la même exigence de classe.

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

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

Sémantique de référence des protocoles de classe uniquement

L'utilisation d'un protocole de classe uniquement permet une sémantique de référence lorsque le type conforme est inconnu.

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

Dans cet exemple, Foo étant un protocole de classe uniquement, l'affectation à la bar est valide car le compilateur sait que foo est un type de classe et qu'il a donc une sémantique de référence.

Si Foo n'était pas un protocole de classe uniquement, une erreur de compilation serait générée - car le type conforme pourrait être un type de valeur , ce qui nécessiterait une annotation var pour pouvoir être muté.

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
}

Variables faibles de type de protocole

Lors de l'application du modificateur weak à une variable de type protocole, ce type de protocole doit être uniquement de classe, car weak ne peut être appliqué qu'à des types de référence.

weak var weakReference : ClassOnlyProtocol?

Implémentation du protocole Hashable

Les types utilisés dans Sets et Dictionaries(key) doivent être conformes au protocole Hashable qui hérite du protocole Equatable .

Le type personnalisé conforme au protocole Hashable doit implémenter

  • Une propriété calculée hashValue
  • Définissez l'un des opérateurs d'égalité, à savoir == ou != .

L'exemple suivant implémente le protocole Hashable pour une struct personnalisée:

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

Remarque : il n'est pas nécessaire que différentes valeurs du type personnalisé aient des valeurs de hachage différentes, les collisions sont acceptables. Si les valeurs de hachage sont égales, l'opérateur d'égalité sera utilisé pour déterminer la véritable égalité.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow