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é
children
qui liste l’ensemble des propriétés de notre élément. - utiliser la propriété
displayStyle
qui donne le style de l’élément. - utiliser la propriété
subjectType
qui donne leType
de l’élément. - appeler la méthode
superclassMirror
pour 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é
children
retourne une liste vide avec les objets en Objective-C. - La propriété
children
ne 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échildren
ne 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
.