Swift Language
Réflexion
Recherche…
Syntaxe
- Miroir (reflétant: instance) // Initialise un miroir avec l'objet à réfléchir
- mirror.displayStyle // Style d'affichage utilisé pour les terrains de jeux Xcode
- mirror.description // Représentation textuelle de cette instance, voir CustomStringConvertible
- mirror.subjectType // Retourne le type du sujet réfléchi
- mirror.superclassMirror // Retourne le miroir de la super-classe du sujet réfléchi
Remarques
- Remarques générales:
Un Mirror
est une struct
utilisée dans l'introspection d'un objet dans Swift. Sa propriété la plus importante est le tableau des enfants. Un cas d'utilisation possible est de sérialiser une structure pour les Core Data
. Cela se fait en convertissant une struct
en un NSManagedObject
.
- Utilisation de base pour Mirror Remarques:
La propriété children
d'un Mirror
est un tableau d'objets enfants provenant de l'objet que l'instance miroir reflète. Un objet child
a deux propriétés label
et value
. Par exemple, un enfant peut être une propriété avec le title
et la valeur de Game of Thrones: A Song of Ice and Fire
.
Utilisation de base pour miroir
Créer la classe pour être le sujet du miroir
class Project {
var title: String = ""
var id: Int = 0
var platform: String = ""
var version: Int = 0
var info: String?
}
Créer une instance qui sera effectivement le sujet du miroir. Ici aussi, vous pouvez ajouter des valeurs aux propriétés de la classe Project.
let sampleProject = Project()
sampleProject.title = "MirrorMirror"
sampleProject.id = 199
sampleProject.platform = "iOS"
sampleProject.version = 2
sampleProject.info = "test app for Reflection"
Le code ci-dessous montre la création de l'instance Mirror. La propriété children du miroir est AnyForwardCollection<Child>
où Child
est typealias tuple pour la propriété et la valeur de l'objet. Child
avait une label: String
et value: Any
.
let projectMirror = Mirror(reflecting: sampleProject)
let properties = projectMirror.children
print(properties.count) //5
print(properties.first?.label) //Optional("title")
print(properties.first!.value) //MirrorMirror
print()
for property in properties {
print("\(property.label!):\(property.value)")
}
Sortie dans Playground ou Console dans Xcode pour la boucle for ci-dessus.
title:MirrorMirror
id:199
platform:iOS
version:2
info:Optional("test app for Reflection")
Testé sur Playground sur Xcode 8 beta 2
Obtenir le type et les noms des propriétés d'une classe sans avoir à l'instancier
L'utilisation de la classe Swift Mirror
fonctionne si vous souhaitez extraire le nom , la valeur et le type (Swift 3: type(of: value)
, Swift 2: value.dynamicType
) des propriétés d'une instance d'une certaine classe.
Si votre classe hérite de NSObject
, vous pouvez utiliser la méthode class_copyPropertyList
avec property_getAttributes
pour rechercher le nom et les types de propriétés d'une classe, sans en avoir une instance . J'ai créé un projet sur Github pour cela, mais voici le code:
func getTypesOfProperties(in clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}
func getTypeOf(property: objc_property_t) -> Any {
guard let attributesAsNSString: NSString = NSString(utf8String: property_getAttributes(property)) else { return Any.self }
let attributes = attributesAsNSString as String
let slices = attributes.components(separatedBy: "\"")
guard slices.count > 1 else { return getPrimitiveDataType(withAttributes: attributes) }
let objectClassName = slices[1]
let objectClass = NSClassFromString(objectClassName) as! NSObject.Type
return objectClass
}
func getPrimitiveDataType(withAttributes attributes: String) -> Any {
guard let letter = attributes.substring(from: 1, to: 2), let type = primitiveDataTypes[letter] else { return Any.self }
return type
}
Où primitiveDataTypes
est un dictionnaire mappant une lettre dans la chaîne d'attribut à un type de valeur:
let primitiveDataTypes: Dictionary<String, Any> = [
"c" : Int8.self,
"s" : Int16.self,
"i" : Int32.self,
"q" : Int.self, //also: Int64, NSInteger, only true on 64 bit platforms
"S" : UInt16.self,
"I" : UInt32.self,
"Q" : UInt.self, //also UInt64, only true on 64 bit platforms
"B" : Bool.self,
"d" : Double.self,
"f" : Float.self,
"{" : Decimal.self
]
func getNameOf(property: objc_property_t) -> String? {
guard let name: NSString = NSString(utf8String: property_getName(property)) else { return nil }
return name as String
}
Il peut extraire le NSObject.Type
de toutes les propriétés dont le type de classe hérite de NSObject
telles que NSDate
(Swift3: Date
), NSString
(Swift3: String
?) Et NSNumber
, mais il est NSNumber
dans le type Any
(comme vous pouvez le voir type de la valeur du dictionnaire renvoyé par la méthode). Cela est dû aux limitations des value types
de value types
tels que Int, Int32, Bool. Comme ces types n'héritent pas de NSObject, appeler .self
, par exemple, un objet Int- Int.self
ne retourne pas NSObject.Type, mais plutôt le type Any
. Ainsi, la méthode renvoie Dictionary<String, Any>?
et pas Dictionary<String, NSObject.Type>?
.
Vous pouvez utiliser cette méthode comme ceci:
class Book: NSObject {
let title: String
let author: String?
let numberOfPages: Int
let released: Date
let isPocket: Bool
init(title: String, author: String?, numberOfPages: Int, released: Date, isPocket: Bool) {
self.title = title
self.author = author
self.numberOfPages = numberOfPages
self.released = released
self.isPocket = isPocket
}
}
guard let types = getTypesOfProperties(in: Book.self) else { return }
for (name, type) in types {
print("'\(name)' has type '\(type)'")
}
// Prints:
// 'title' has type 'NSString'
// 'numberOfPages' has type 'Int'
// 'author' has type 'NSString'
// 'released' has type 'NSDate'
// 'isPocket' has type 'Bool'
Vous pouvez également essayer de convertir le Any
en NSObject.Type
, qui réussira pour toutes les propriétés héritant de NSObject
, vous pourrez alors vérifier le type en utilisant l'opérateur standard ==
:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(in: Book.self) else { return }
for (name, type) in types {
if let objectType = type as? NSObject.Type {
if objectType == NSDate.self {
print("Property named '\(name)' has type 'NSDate'")
} else if objectType == NSString.self {
print("Property named '\(name)' has type 'NSString'")
}
}
}
}
Si vous déclarez cet opérateur personnalisé ==
:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
Vous pouvez même vérifier le type de type de value types
comme ceci:
func checkPropertiesOfBook() {
guard let types = getTypesOfProperties(in: Book.self) else { return }
for (name, type) in types {
if type == Int.self {
print("Property named '\(name)' has type 'Int'")
} else if type == Bool.self {
print("Property named '\(name)' has type 'Bool'")
}
}
}
LIMITES Cette solution ne fonctionne pas lorsque les value types
sont optionnels. Si vous avez déclaré une propriété dans votre sous-classe NSObject comme ceci: var myOptionalInt: Int?
, le code ci-dessus ne trouvera pas cette propriété car la méthode class_copyPropertyList
ne contient pas de types de valeur facultatifs.