Dans ce cours nous allons traiter de la réflexion (reflection en anglais) du langage Swift 2. Avant d’aller plus loin, et pour clarifier les choses, la réflexion est la capacité d’un programme à s’examiner lui-même, voir à pouvoir se modifier lui-même. On parle aussi de langage réflexif. Swift permet uniquement de faire de l’introspection (c’est à dire à s’examiner lui-même). Il ne peut malheureusement pas faire de l’intercession (c’est à dire de modifier la structure de son code au runtime), du moins pour le moment, car il désire garantir la sécurité d’exécution du programme. Les capacités d’introspection de Swift sont basées autour d’une structure appelée Mirror dont on se sert pour créer un miroir des objets que l’on souhaite inspecter.
La création d’un miroir
Un miroir (type Mirror) est une struct qui va nous permettre d’inspecter n’importe quel type (Any) qu’on lui passe en paramètre. La manière la plus simple pour créer un miroir en Swift est d’utiliser cet initialiseur :
public init(reflecting subject: Any)
L’argument de l’initialiseur est type Any ce permet de créer des miroirs de n’importe quel type comme des struct, class, enum, Tuple, Array, etc. Maintenant pour illustrer son fonctionnement commençons par définir une structure appelé Voiture et créons une variable clioIV du même type:
struct Voiture {
let marque: String
let modele: String
}
let clioIV = Voiture(marque: "Renault", modele: "Clio IV")
Et maintenant créons un miroir de l’objet clioIV :
let miroir = Mirror(reflecting: clioIV)
print(miroir)
// Affiche : Mirror for Voiture
Il y a trois initialiseurs supplémentaires pour le type Mirror mais ils sont là pour créer des miroirs personnalisés. Maintenant que nous avons vu comment créer un miroir, nous allons voir qu’est ce que c’est et surtout à quoi cela peut servir.
Les miroirs
Un Mirror contient plusieurs propriétés pour nous aider à identifier les informations que vous souhaiter inspecter.
La première information importante est l’énumération DisplayStyle qui permet de connaitre le type de l’objet que l’on inspecte :
public enum DisplayStyle {
case Struct
case Class
case Enum
case Tuple
case Optional
case Collection
case Dictionary
case Set
}
Ici ce sont les types supportés par l’API de réflexion en Swift. Comme nous l’avons vu plus haut, la réflexion peut se faire normalement avec n’importe quel type (Any), et il y a beaucoup d’objet dans la bibliothèque standard de Swift qui sont de type Any mais qui ne figurent pas dans l’énumération DisplayStyle du dessus. Alors qu’est-ce qui se passe lorsque vous essayez de faire de l’introspection sur une closure par exemple ?
let closure = { (x: Int) -> Int in x * 2 }
let miroir = Mirror(reflecting: closure)
print(miroir.displayStyle)
// Affiche : nil
Dans cet exemple, vous pouvez voir que l’on peut créer un miroir d’une closure par contre le DisplayStyle est nul.
Le type Mirror définit aussi un typealias pour connaitre les enfants de l’élément :
public typealias Child = (label: String?, value: Any)
Un enfant (Child) c’est un tuple qui contient un label de type optionnel de String et une value de type Any. Si vous vous demandez pourquoi le label est optionnel c’est parce que tous les types n’ont pas forcément des propriétés comme les Collection ou les Tuple.
À l’usage
Reprenons notre miroir de clioIV. Qu’est ce que nous pouvons faire avec ?
- utiliser la propriété
childrenqui liste l’ensemble des propriétés de notre élément. - utiliser la propriété
displayStylequi donne le style de l’élément. - utiliser la propriété
subjectTypequi donne leTypede l’élément. - appeler la méthode
superclassMirrorpour récupérer le miroir de la super-classe de l’élément.
Étudions chacun de ces éléments d’un peu plus prêt.
displayStyle
Comme nous l’avons vu plus haut, cette propriété retourne le style (DisplayStyle) de l’élément. Si vous inspecté un élément avec un type inconnu (comme par example une closure) vous aurez un optional vide :
print(miroir.displayStyle)
// Affiche : Optional(Swift.Mirror.DisplayStyle.Struct)
children
Cette propriété retourne un AnyForwardCollection avec la liste de tous les enfants que l’élément contient. Les enfants ne sont pas limité aux entrées des Collection. Toutes les propriétés de la structure ou de la classe, par example, sont aussi des enfants retournés par cette propriété :
for case let (label?, value) in miroir.children {
print("\(label): \(value)")
}
// Affiche :
// marque: Renault
// modele: Clio IV
subjectType
Cette propriété retourne le type de l’élément que le miroir reflète. C’est équivalent à la propriété dynamicType :
print(miroir.subjectType)
// Affiche : (Voiture #1)
print(clioIV.dynamicType)
// Affiche : (Voiture #1)
print(Mirror(reflecting: 5).subjectType)
// Affiche : Int
print(Mirror(reflecting: "test").subjectType)
// Affiche : String
print(Mirror(reflecting: NSData()).subjectType)
// Affiche : _NSZeroData
superclassMirror
Cette méthode retourne le miroir de la super-classe de l’élément. Si l’élément n’est pas une classe, la méthode retourne un optionnel vide.
print(miroir.superclassMirror())
// Affiche : nil
print(Mirror(reflecting: UINavigationController()).superclassMirror())
// Affiche : Optional(Mirror for UIViewController)
Limitations
L’introspection en Swift est tout de même très limité :
- On ne peut pas lister les méthodes.
- La propriété
childrenretourne une liste vide avec les objets en Objective-C. - La propriété
childrenne prend pas en compte les propriétés calculées, par example :var nombreDePorte: UInt { return 4 } - Si l’élément est une
class, la propriétéchildrenne reporte pas les propriétés de la super-classe :class Person { var name = "Bruce Wayne" } class Superhero: Person { var hasSuperpowers = true } let miroir = Mirror(reflecting: Superhero()) print(miroir.children.flatMap { $0.label }) // Affiche : ["hasSuperpowers"]Vous pouvez passer outre ce problème en utilisant la méthode
superclassMirror.








