introspection-reflection-swift

La réflexion en Swift 2

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 le Type 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.

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (1 votes, average: 5,00 out of 5)
Loading...
Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInPin on PinterestShare on RedditDigg this

Aucun commentaire

Time limit is exhausted. Please reload CAPTCHA.