Swift Language
Protocoles
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:
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]
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é.