Le protocole est une fonctionnalité très puissante du Langage de programmation Swift .
ce qui a causé la crise financière grecque
Protocoles sont utilisés pour définir un «modèle de méthodes, de propriétés et d'autres exigences qui conviennent à une tâche ou à une fonctionnalité particulière».
Swift vérifie les problèmes de conformité du protocole au moment de la compilation, permettant aux développeurs de découvrir certains bogues fatals dans le code avant même d'exécuter le programme. Les protocoles permettent aux développeurs d’écrire du code flexible et extensible dans Swift sans avoir à compromettre l’expressivité du langage.
Swift va encore plus loin dans l'utilisation des protocoles en fournissant des solutions de contournement à certaines des bizarreries et limitations les plus courantes des interfaces qui affectent de nombreux autres langages de programmation.
Dans les versions antérieures de Swift, il était possible d'étendre uniquement les classes, les structures et les énumérations, comme c'est le cas dans de nombreux langages de programmation modernes. Cependant, depuis la version 2 de Swift, il est également devenu possible d'étendre les protocoles.
Cet article examine comment les protocoles de Swift peuvent être utilisés pour écrire du code réutilisable et maintenable et comment les modifications apportées à une grande base de code orientée protocole peuvent être consolidées en un seul endroit grâce à l'utilisation d'extensions de protocole.
Qu'est-ce qu'un protocole?
Dans sa forme la plus simple, un protocole est une interface qui décrit certaines propriétés et méthodes. Tout type conforme à un protocole doit remplir les propriétés spécifiques définies dans le protocole avec les valeurs appropriées et implémenter ses méthodes requises. Par exemple:
protocol Queue { var count: Int { get } mutating func push(_ element: Int) mutating func pop() -> Int }
Le protocole Queue décrit une file d'attente contenant des éléments entiers. La syntaxe est assez simple.
A l'intérieur du bloc de protocole, lorsque nous décrivons une propriété, nous devons spécifier si la propriété est uniquement accessible { get }
ou à la fois accessible et paramétrable { get set }
. Dans notre cas, la variable Count (de type Int
) est uniquement accessible.
Si un protocole exige qu'une propriété soit accessible et définissable, cette exigence ne peut pas être remplie par une propriété stockée constante ou une propriété calculée en lecture seule.
Si le protocole ne requiert qu'une propriété pour être gérable, l'exigence peut être satisfaite par n'importe quel type de propriété, et il est valide que la propriété soit également configurable, si cela est utile pour votre propre code.
Pour les fonctions définies dans un protocole, il est important d'indiquer si la fonction va changer le contenu avec mutating
mot-clé. En dehors de cela, la signature d'une fonction suffit comme définition.
Pour se conformer à un protocole, un type doit fournir toutes les propriétés d'instance et implémenter toutes les méthodes décrites dans le protocole. Ci-dessous, par exemple, une structure Container
conforme à notre Queue
protocole. La structure stocke essentiellement les Int
s poussés dans un tableau privé items
.
struct Container: Queue { private var items: [Int] = [] var count: Int { return items.count } mutating func push(_ element: Int) { items.append(element) } mutating func pop() -> Int { return items.removeFirst() } }
Cependant, notre protocole de file d'attente actuel présente un inconvénient majeur.
Seuls les conteneurs qui traitent des Int
s peuvent se conformer à ce protocole.
Nous pouvons supprimer cette limitation en utilisant la fonction «types associés». Les types associés fonctionnent comme des génériques. Pour démontrer, modifions le protocole de file d'attente pour utiliser les types associés:
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Désormais, le protocole Queue permet le stockage de tout type d'articles.
Dans la mise en œuvre de Container
structure, le compilateur détermine le type associé à partir du contexte (c'est-à-dire le type de retour de méthode et les types de paramètres). Cette approche nous permet de créer un Container
structure avec un type d'élément générique. Par exemple:
class Container: Queue { private var items: [Item] = [] var count: Int { return items.count } func push(_ element: Item) { items.append(element) } func pop() -> Item { return items.removeFirst() } }
L'utilisation de protocoles simplifie l'écriture de code dans de nombreux cas.
Par exemple, tout objet qui représente une erreur peut se conformer à Error
(ou LocalizedError
, au cas où nous voudrions fournir des descriptions localisées) protocole.
La même logique de gestion des erreurs peut ensuite être appliquée à l'un de ces objets d'erreur dans tout votre code. Par conséquent, vous n’avez pas besoin d’utiliser d’objet spécifique (comme NSError en Objective-C) pour représenter les erreurs, vous pouvez utiliser n’importe quel type conforme à Error
ou LocalizedError
protocoles.
Vous pouvez même étendre le type String pour le rendre conforme à LocalizedError
protocole et lancez des chaînes comme des erreurs.
extension String: LocalizedError { public var errorDescription: String? { Return NSLocalizedString(self, comment:””) } } throw “Unfortunately something went wrong” func handle(error: Error) { print(error.localizedDescription) }
Les extensions de protocole s'appuient sur la génialité des protocoles. Ils nous permettent de:
Fournit une implémentation par défaut des méthodes de protocole et des valeurs par défaut des propriétés de protocole, les rendant ainsi «facultatives». Les types qui se conforment à un protocole peuvent fournir leurs propres implémentations ou utiliser celles par défaut.
Ajoutez la mise en œuvre de méthodes supplémentaires non décrites dans le protocole et «décorez» tous les types conformes au protocole avec ces méthodes supplémentaires. Cette fonctionnalité nous permet d'ajouter des méthodes spécifiques à plusieurs types qui sont déjà conformes au protocole sans avoir à modifier chaque type individuellement.
Créons un autre protocole:
protocol ErrorHandler { func handle(error: Error) }
Ce protocole décrit les objets chargés de gérer les erreurs survenant dans une application. Par exemple:
struct Handler: ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Ici, nous imprimons simplement la description localisée de l'erreur. Avec l'extension de protocole, nous pouvons faire de cette implémentation la valeur par défaut.
extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Faire cela rend le handle
méthode facultative en fournissant une implémentation par défaut.
La possibilité d'étendre un protocole existant avec des comportements par défaut est assez puissante, permettant aux protocoles de se développer et de s'étendre sans avoir à se soucier de la compatibilité du code existant.
Qu'est-ce que le développement axé sur le comportement
Nous avons donc fourni une implémentation par défaut de handle
méthode, mais l'impression sur la console n'est pas très utile pour l'utilisateur final.
Nous préférerions probablement leur montrer une sorte de vue d'alerte avec une description localisée dans les cas où le gestionnaire d'erreurs est un contrôleur de vue. Pour ce faire, nous pouvons étendre le ErrorHandler
protocole, mais peut limiter l'extension à s'appliquer uniquement dans certains cas (c'est-à-dire lorsque le type est un contrôleur de vue).
Swift nous permet d'ajouter de telles conditions aux extensions de protocole en utilisant where
mot-clé.
extension ErrorHandler where Self: UIViewController { func handle(error: Error) { let alert = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) let action = UIAlertAction(title: 'OK', style: .cancel, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) } }
Soi (avec «S» majuscule) dans l'extrait de code ci-dessus fait référence au type (structure, classe ou énumération). En spécifiant que nous étendons le protocole uniquement pour les types qui héritent de UIViewController
, nous pouvons utiliser UIViewController
méthodes spécifiques (telles que present(viewControllerToPresnt: animated: completion)
).
Désormais, tous les contrôleurs de vue conformes à ErrorHandler
protocole ont leur propre implémentation par défaut de handle
méthode qui affiche une vue d'alerte avec une description localisée.
Supposons qu'il existe deux protocoles, qui ont tous deux une méthode avec la même signature.
protocol P1 { func method() //some other methods } protocol P2 { func method() //some other methods }
Les deux protocoles ont une extension avec une implémentation par défaut de cette méthode.
extension P1 { func method() { print('Method P1') } } extension P2 { func method() { print('Method P2') } }
Supposons maintenant qu’il existe un type conforme aux deux protocoles.
struct S: P1, P2 { }
Dans ce cas, nous avons un problème avec l'implémentation de méthode ambiguë. Le type n'indique pas clairement la mise en œuvre de la méthode à utiliser. En conséquence, nous obtenons une erreur de compilation. Pour résoudre ce problème, nous devons ajouter l'implémentation de la méthode au type.
struct S: P1, P2 { func method() { print('Method S') } }
De nombreux langages de programmation orientés objet sont confrontés à des limitations entourant la résolution des définitions d'extensions ambiguës. Swift gère cela de manière assez élégante grâce aux extensions de protocole en permettant au programmeur de prendre le contrôle là où le compilateur échoue.
Jetons un œil à Queue
protocole une fois de plus.
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Chaque type conforme à Queue
protocole a un count
propriété d'instance qui définit le nombre d'éléments stockés. Cela nous permet, entre autres, de comparer ces types pour décider lequel est le plus grand. Nous pouvons ajouter cette méthode via l'extension de protocole.
extension Queue { func compare(queue: Q) -> ComparisonResult where Q: Queue { if count queue.count { return .orderedAscending } return .orderedSame } }
Cette méthode n'est pas décrite dans le Queue
protocole lui-même car il n'est pas lié à la fonctionnalité de file d'attente.
Il ne s'agit donc pas d'une implémentation par défaut de la méthode de protocole, mais plutôt d'une nouvelle implémentation de méthode qui «décore» tous les types conformes à Queue
protocole. Sans les extensions de protocole, nous devrons ajouter cette méthode à chaque type séparément.
Les extensions de protocole peuvent sembler assez similaires à l'utilisation d'une classe de base, mais l'utilisation d'extensions de protocole présente plusieurs avantages. Ceux-ci incluent, mais ne sont pas nécessairement limités à:
Étant donné que les classes, les structures et les énumérations peuvent se conformer à plus d'un protocole, ils peuvent prendre l'implémentation par défaut de plusieurs protocoles. Ceci est conceptuellement similaire à l'héritage multiple dans d'autres langues.
Les protocoles peuvent être adoptés par les classes, les structures et les énumérations, tandis que les classes de base et l'héritage ne sont disponibles que pour les classes.
En plus d'étendre vos propres protocoles, vous pouvez étendre les protocoles de la bibliothèque standard Swift. Par exemple, si nous voulons trouver la taille moyenne de la collection de files d'attente, nous pouvons le faire en étendant la norme Collection
protocole.
Les structures de données de séquence fournies par la bibliothèque standard de Swift, dont les éléments peuvent être parcourus et accessibles via un indice indexé, sont généralement conformes au Collection
protocole. Grâce à l'extension de protocole, il est possible d'étendre toutes ces structures de données de bibliothèque standard ou d'étendre certaines d'entre elles de manière sélective.
Remarque: Le protocole anciennement connu sous le nom de
CollectionType
dans Swift 2.x a été renommé enCollection
dans Swift 3.
extension Collection where Iterator.Element: Queue { func avgSize() -> Int { let size = map { Une introduction à la programmation orientée protocole dans Swift
Le protocole est une fonctionnalité très puissante du Langage de programmation Swift .
Protocoles sont utilisés pour définir un «modèle de méthodes, de propriétés et d'autres exigences qui conviennent à une tâche ou à une fonctionnalité particulière».
Swift vérifie les problèmes de conformité du protocole au moment de la compilation, permettant aux développeurs de découvrir certains bogues fatals dans le code avant même d'exécuter le programme. Les protocoles permettent aux développeurs d’écrire du code flexible et extensible dans Swift sans avoir à compromettre l’expressivité du langage.
Swift va encore plus loin dans l'utilisation des protocoles en fournissant des solutions de contournement à certaines des bizarreries et limitations les plus courantes des interfaces qui affectent de nombreux autres langages de programmation.
Dans les versions antérieures de Swift, il était possible d'étendre uniquement les classes, les structures et les énumérations, comme c'est le cas dans de nombreux langages de programmation modernes. Cependant, depuis la version 2 de Swift, il est également devenu possible d'étendre les protocoles.
Cet article examine comment les protocoles de Swift peuvent être utilisés pour écrire du code réutilisable et maintenable et comment les modifications apportées à une grande base de code orientée protocole peuvent être consolidées en un seul endroit grâce à l'utilisation d'extensions de protocole.
Qu'est-ce qu'un protocole?
Dans sa forme la plus simple, un protocole est une interface qui décrit certaines propriétés et méthodes. Tout type conforme à un protocole doit remplir les propriétés spécifiques définies dans le protocole avec les valeurs appropriées et implémenter ses méthodes requises. Par exemple:
protocol Queue { var count: Int { get } mutating func push(_ element: Int) mutating func pop() -> Int }
Le protocole Queue décrit une file d'attente contenant des éléments entiers. La syntaxe est assez simple.
A l'intérieur du bloc de protocole, lorsque nous décrivons une propriété, nous devons spécifier si la propriété est uniquement accessible { get }
ou à la fois accessible et paramétrable { get set }
. Dans notre cas, la variable Count (de type Int
) est uniquement accessible.
Si un protocole exige qu'une propriété soit accessible et définissable, cette exigence ne peut pas être remplie par une propriété stockée constante ou une propriété calculée en lecture seule.
Si le protocole ne requiert qu'une propriété pour être gérable, l'exigence peut être satisfaite par n'importe quel type de propriété, et il est valide que la propriété soit également configurable, si cela est utile pour votre propre code.
Pour les fonctions définies dans un protocole, il est important d'indiquer si la fonction va changer le contenu avec mutating
mot-clé. En dehors de cela, la signature d'une fonction suffit comme définition.
Pour se conformer à un protocole, un type doit fournir toutes les propriétés d'instance et implémenter toutes les méthodes décrites dans le protocole. Ci-dessous, par exemple, une structure Container
conforme à notre Queue
protocole. La structure stocke essentiellement les Int
s poussés dans un tableau privé items
.
struct Container: Queue { private var items: [Int] = [] var count: Int { return items.count } mutating func push(_ element: Int) { items.append(element) } mutating func pop() -> Int { return items.removeFirst() } }
Cependant, notre protocole de file d'attente actuel présente un inconvénient majeur.
Seuls les conteneurs qui traitent des Int
s peuvent se conformer à ce protocole.
Nous pouvons supprimer cette limitation en utilisant la fonction «types associés». Les types associés fonctionnent comme des génériques. Pour démontrer, modifions le protocole de file d'attente pour utiliser les types associés:
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Désormais, le protocole Queue permet le stockage de tout type d'articles.
Dans la mise en œuvre de Container
structure, le compilateur détermine le type associé à partir du contexte (c'est-à-dire le type de retour de méthode et les types de paramètres). Cette approche nous permet de créer un Container
structure avec un type d'élément générique. Par exemple:
class Container: Queue { private var items: [Item] = [] var count: Int { return items.count } func push(_ element: Item) { items.append(element) } func pop() -> Item { return items.removeFirst() } }
L'utilisation de protocoles simplifie l'écriture de code dans de nombreux cas.
Par exemple, tout objet qui représente une erreur peut se conformer à Error
(ou LocalizedError
, au cas où nous voudrions fournir des descriptions localisées) protocole.
La même logique de gestion des erreurs peut ensuite être appliquée à l'un de ces objets d'erreur dans tout votre code. Par conséquent, vous n’avez pas besoin d’utiliser d’objet spécifique (comme NSError en Objective-C) pour représenter les erreurs, vous pouvez utiliser n’importe quel type conforme à Error
ou LocalizedError
protocoles.
Vous pouvez même étendre le type String pour le rendre conforme à LocalizedError
protocole et lancez des chaînes comme des erreurs.
extension String: LocalizedError { public var errorDescription: String? { Return NSLocalizedString(self, comment:””) } } throw “Unfortunately something went wrong” func handle(error: Error) { print(error.localizedDescription) }
Les extensions de protocole s'appuient sur la génialité des protocoles. Ils nous permettent de:
Fournit une implémentation par défaut des méthodes de protocole et des valeurs par défaut des propriétés de protocole, les rendant ainsi «facultatives». Les types qui se conforment à un protocole peuvent fournir leurs propres implémentations ou utiliser celles par défaut.
Ajoutez la mise en œuvre de méthodes supplémentaires non décrites dans le protocole et «décorez» tous les types conformes au protocole avec ces méthodes supplémentaires. Cette fonctionnalité nous permet d'ajouter des méthodes spécifiques à plusieurs types qui sont déjà conformes au protocole sans avoir à modifier chaque type individuellement.
Créons un autre protocole:
protocol ErrorHandler { func handle(error: Error) }
Ce protocole décrit les objets chargés de gérer les erreurs survenant dans une application. Par exemple:
struct Handler: ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Ici, nous imprimons simplement la description localisée de l'erreur. Avec l'extension de protocole, nous pouvons faire de cette implémentation la valeur par défaut.
extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } }
Faire cela rend le handle
méthode facultative en fournissant une implémentation par défaut.
La possibilité d'étendre un protocole existant avec des comportements par défaut est assez puissante, permettant aux protocoles de se développer et de s'étendre sans avoir à se soucier de la compatibilité du code existant.
Nous avons donc fourni une implémentation par défaut de handle
méthode, mais l'impression sur la console n'est pas très utile pour l'utilisateur final.
Nous préférerions probablement leur montrer une sorte de vue d'alerte avec une description localisée dans les cas où le gestionnaire d'erreurs est un contrôleur de vue. Pour ce faire, nous pouvons étendre le ErrorHandler
protocole, mais peut limiter l'extension à s'appliquer uniquement dans certains cas (c'est-à-dire lorsque le type est un contrôleur de vue).
Swift nous permet d'ajouter de telles conditions aux extensions de protocole en utilisant where
mot-clé.
extension ErrorHandler where Self: UIViewController { func handle(error: Error) { let alert = UIAlertController(title: nil, message: error.localizedDescription, preferredStyle: .alert) let action = UIAlertAction(title: 'OK', style: .cancel, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) } }
Soi (avec «S» majuscule) dans l'extrait de code ci-dessus fait référence au type (structure, classe ou énumération). En spécifiant que nous étendons le protocole uniquement pour les types qui héritent de UIViewController
, nous pouvons utiliser UIViewController
méthodes spécifiques (telles que present(viewControllerToPresnt: animated: completion)
).
Désormais, tous les contrôleurs de vue conformes à ErrorHandler
protocole ont leur propre implémentation par défaut de handle
méthode qui affiche une vue d'alerte avec une description localisée.
Supposons qu'il existe deux protocoles, qui ont tous deux une méthode avec la même signature.
protocol P1 { func method() //some other methods } protocol P2 { func method() //some other methods }
Les deux protocoles ont une extension avec une implémentation par défaut de cette méthode.
extension P1 { func method() { print('Method P1') } } extension P2 { func method() { print('Method P2') } }
Supposons maintenant qu’il existe un type conforme aux deux protocoles.
struct S: P1, P2 { }
Dans ce cas, nous avons un problème avec l'implémentation de méthode ambiguë. Le type n'indique pas clairement la mise en œuvre de la méthode à utiliser. En conséquence, nous obtenons une erreur de compilation. Pour résoudre ce problème, nous devons ajouter l'implémentation de la méthode au type.
struct S: P1, P2 { func method() { print('Method S') } }
De nombreux langages de programmation orientés objet sont confrontés à des limitations entourant la résolution des définitions d'extensions ambiguës. Swift gère cela de manière assez élégante grâce aux extensions de protocole en permettant au programmeur de prendre le contrôle là où le compilateur échoue.
Jetons un œil à Queue
protocole une fois de plus.
protocol Queue { associatedtype ItemType var count: Int { get } func push(_ element: ItemType) func pop() -> ItemType }
Chaque type conforme à Queue
protocole a un count
propriété d'instance qui définit le nombre d'éléments stockés. Cela nous permet, entre autres, de comparer ces types pour décider lequel est le plus grand. Nous pouvons ajouter cette méthode via l'extension de protocole.
extension Queue { func compare(queue: Q) -> ComparisonResult where Q: Queue { if count queue.count { return .orderedAscending } return .orderedSame } }
Cette méthode n'est pas décrite dans le Queue
protocole lui-même car il n'est pas lié à la fonctionnalité de file d'attente.
Il ne s'agit donc pas d'une implémentation par défaut de la méthode de protocole, mais plutôt d'une nouvelle implémentation de méthode qui «décore» tous les types conformes à Queue
protocole. Sans les extensions de protocole, nous devrons ajouter cette méthode à chaque type séparément.
Les extensions de protocole peuvent sembler assez similaires à l'utilisation d'une classe de base, mais l'utilisation d'extensions de protocole présente plusieurs avantages. Ceux-ci incluent, mais ne sont pas nécessairement limités à:
Étant donné que les classes, les structures et les énumérations peuvent se conformer à plus d'un protocole, ils peuvent prendre l'implémentation par défaut de plusieurs protocoles. Ceci est conceptuellement similaire à l'héritage multiple dans d'autres langues.
Les protocoles peuvent être adoptés par les classes, les structures et les énumérations, tandis que les classes de base et l'héritage ne sont disponibles que pour les classes.
En plus d'étendre vos propres protocoles, vous pouvez étendre les protocoles de la bibliothèque standard Swift. Par exemple, si nous voulons trouver la taille moyenne de la collection de files d'attente, nous pouvons le faire en étendant la norme Collection
protocole.
Les structures de données de séquence fournies par la bibliothèque standard de Swift, dont les éléments peuvent être parcourus et accessibles via un indice indexé, sont généralement conformes au Collection
protocole. Grâce à l'extension de protocole, il est possible d'étendre toutes ces structures de données de bibliothèque standard ou d'étendre certaines d'entre elles de manière sélective.
Remarque: Le protocole anciennement connu sous le nom de
CollectionType
dans Swift 2.x a été renommé enCollection
dans Swift 3.
extension Collection where Iterator.Element: Queue { func avgSize() -> Int { let size = map { $0.count }.reduce(0, +) return Int(round(Double(size) / Double(count.toIntMax()))) } }
Nous pouvons maintenant calculer la taille moyenne de n'importe quelle collection de files d'attente (Array
, Set
, etc.). Sans les extensions de protocole, nous aurions dû ajouter cette méthode à chaque type de collection séparément.
Dans la bibliothèque standard Swift, les extensions de protocole sont utilisées pour implémenter, par exemple, des méthodes telles que map
, filter
, reduce
, etc.
extension Collection { public func map(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T] { } }
Comme je l'ai dit plus tôt, les extensions de protocole nous permettent d'ajouter des implémentations par défaut de certaines méthodes et d'ajouter de nouvelles implémentations de méthode. Mais quelle est la différence entre ces deux fonctionnalités? Revenons au gestionnaire d'erreurs et découvrons-le.
protocol ErrorHandler { func handle(error: Error) } extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } } struct Handler: ErrorHandler { func handle(error: Error) { fatalError('Unexpected error occurred') } } enum ApplicationError: Error { case other } let handler: Handler = Handler() handler.handle(error: ApplicationError.other)
Le résultat est une erreur fatale.
Supprimez maintenant le handle(error: Error)
déclaration de méthode du protocole.
protocol ErrorHandler { }
Le résultat est le même: une erreur fatale.
Cela signifie-t-il qu'il n'y a aucune différence entre l'ajout d'une implémentation par défaut de la méthode de protocole et l'ajout d'une nouvelle implémentation de méthode au protocole?
Non! Une différence existe et vous pouvez la voir en changeant le type de la variable handler
de Handler
à ErrorHandler
.
let handler: ErrorHandler = Handler()
Maintenant, la sortie vers la console est: L’opération n’a pas pu être terminée. (Erreur ApplicationError 0.)
Mais si nous renvoyons la déclaration de la méthode handle (error: Error) au protocole, le résultat reviendra à l'erreur fatale.
protocol ErrorHandler { func handle(error: Error) }
Examinons l’ordre de ce qui se passe dans chaque cas.
Lorsque la déclaration de méthode existe dans le protocole:
Le protocole déclare le handle(error: Error)
et fournit une implémentation par défaut. La méthode est remplacée dans le Handler
la mise en oeuvre. Ainsi, l'implémentation correcte de la méthode est appelée lors de l'exécution, quel que soit le type de la variable.
Lorsque la déclaration de méthode n’existe pas dans le protocole:
Étant donné que la méthode n'est pas déclarée dans le protocole, le type ne peut pas la remplacer. C'est pourquoi l'implémentation d'une méthode appelée dépend du type de la variable.
Si la variable est de type Handler
, l'implémentation de la méthode à partir du type est appelée. Dans le cas où la variable est de type ErrorHandler
, l'implémentation de la méthode à partir de l'extension de protocole est appelée.
Dans cet article, nous avons démontré une partie de la puissance des extensions de protocole dans Swift.
Contrairement aux autres langages de programmation avec interfaces, Swift ne restreint pas les protocoles avec des limitations inutiles. Swift contourne les bizarreries communes de ces langages de programmation en permettant au développeur de résoudre l'ambiguïté si nécessaire.
Avec les protocoles Swift et les extensions de protocole, le code que vous écrivez peut être aussi expressif que la plupart des langages de programmation dynamiques et être toujours de type sécurisé au moment de la compilation. Cela vous permet d'assurer la réutilisabilité et la maintenabilité de votre code et d'apporter des modifications à la base de code de votre application Swift avec plus de confiance.
Nous espérons que cet article vous sera utile et accueillera vos commentaires ou autres informations.
Nous pouvons maintenant calculer la taille moyenne de n'importe quelle collection de files d'attente (Array
, Set
, etc.). Sans les extensions de protocole, nous aurions dû ajouter cette méthode à chaque type de collection séparément.
Dans la bibliothèque standard Swift, les extensions de protocole sont utilisées pour implémenter, par exemple, des méthodes telles que map
, filter
, reduce
, etc.
qu'est-ce qu'un bot discord
extension Collection { public func map(_ transform: (Self.Iterator.Element) throws -> T) rethrows -> [T] { } }
Comme je l'ai dit plus tôt, les extensions de protocole nous permettent d'ajouter des implémentations par défaut de certaines méthodes et d'ajouter de nouvelles implémentations de méthode. Mais quelle est la différence entre ces deux fonctionnalités? Revenons au gestionnaire d'erreurs et découvrons-le.
protocol ErrorHandler { func handle(error: Error) } extension ErrorHandler { func handle(error: Error) { print(error.localizedDescription) } } struct Handler: ErrorHandler { func handle(error: Error) { fatalError('Unexpected error occurred') } } enum ApplicationError: Error { case other } let handler: Handler = Handler() handler.handle(error: ApplicationError.other)
Le résultat est une erreur fatale.
Supprimez maintenant le handle(error: Error)
déclaration de méthode du protocole.
protocol ErrorHandler { }
Le résultat est le même: une erreur fatale.
Cela signifie-t-il qu'il n'y a aucune différence entre l'ajout d'une implémentation par défaut de la méthode de protocole et l'ajout d'une nouvelle implémentation de méthode au protocole?
Non! Une différence existe et vous pouvez la voir en changeant le type de la variable handler
de Handler
à ErrorHandler
.
let handler: ErrorHandler = Handler()
Maintenant, la sortie vers la console est: L’opération n’a pas pu être terminée. (Erreur ApplicationError 0.)
Mais si nous renvoyons la déclaration de la méthode handle (error: Error) au protocole, le résultat reviendra à l'erreur fatale.
protocol ErrorHandler { func handle(error: Error) }
Examinons l’ordre de ce qui se passe dans chaque cas.
Lorsque la déclaration de méthode existe dans le protocole:
Le protocole déclare le handle(error: Error)
et fournit une implémentation par défaut. La méthode est remplacée dans le Handler
la mise en oeuvre. Ainsi, l'implémentation correcte de la méthode est appelée lors de l'exécution, quel que soit le type de la variable.
Lorsque la déclaration de méthode n’existe pas dans le protocole:
Étant donné que la méthode n'est pas déclarée dans le protocole, le type ne peut pas la remplacer. C'est pourquoi l'implémentation d'une méthode appelée dépend du type de la variable.
Si la variable est de type Handler
, l'implémentation de la méthode à partir du type est appelée. Dans le cas où la variable est de type ErrorHandler
, l'implémentation de la méthode à partir de l'extension de protocole est appelée.
Dans cet article, nous avons démontré une partie de la puissance des extensions de protocole dans Swift.
Contrairement aux autres langages de programmation avec interfaces, Swift ne restreint pas les protocoles avec des limitations inutiles. Swift contourne les bizarreries communes de ces langages de programmation en permettant au développeur de résoudre l'ambiguïté si nécessaire.
Avec les protocoles Swift et les extensions de protocole, le code que vous écrivez peut être aussi expressif que la plupart des langages de programmation dynamiques et être toujours de type sécurisé au moment de la compilation. Cela vous permet d'assurer la réutilisabilité et la maintenabilité de votre code et d'apporter des modifications à la base de code de votre application Swift avec plus de confiance.
Nous espérons que cet article vous sera utile et accueillera vos commentaires ou autres informations.