Swift Language
Reflexión
Buscar..
Sintaxis
- Espejo (reflejando: instancia) // Inicializa un espejo con el sujeto a reflejar
- mirror.displayStyle // Estilo de visualización utilizado para los parques infantiles Xcode
- mirror.description // Representación textual de esta instancia, vea CustomStringConvertible
- mirror.subjectType // Devuelve el tipo del tema que se refleja
- mirror.superclassMirror // Devuelve el espejo de la superclase del sujeto que se refleja
Observaciones
- Observaciones generales:
Un Mirror
es una struct
utilizada en la introspección de un objeto en Swift. Su propiedad más destacada es la matriz infantil. Un posible caso de uso es serializar una estructura para Core Data
. Esto se hace al convertir una struct
en un objeto NSManagedObject
.
- Uso básico para comentarios de espejo:
El children
propiedad de un Mirror
es una matriz de objetos secundarios del objeto de la instancia espejo está reflejando. Un objeto child
tiene dos propiedades label
y value
. Por ejemplo, un niño puede ser una propiedad con el nombre title
y el valor de Game of Thrones: A Song of Ice and Fire
.
Uso básico para espejo
Creando la clase para ser el sujeto del espejo.
class Project {
var title: String = ""
var id: Int = 0
var platform: String = ""
var version: Int = 0
var info: String?
}
Creando una instancia que realmente será el sujeto del espejo. También aquí puede agregar valores a las propiedades de la clase Proyecto.
let sampleProject = Project()
sampleProject.title = "MirrorMirror"
sampleProject.id = 199
sampleProject.platform = "iOS"
sampleProject.version = 2
sampleProject.info = "test app for Reflection"
El siguiente código muestra la creación de la instancia de Mirror. La propiedad children del espejo es una AnyForwardCollection<Child>
donde Child
es typealias tuple para la propiedad y el valor del sujeto. Child
tenía una label: String
y 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)")
}
Salida en Playground o Console en Xcode para el bucle for anterior.
title:MirrorMirror
id:199
platform:iOS
version:2
info:Optional("test app for Reflection")
Probado en Playground en Xcode 8 beta 2
Obtención de tipos y nombres de propiedades para una clase sin tener que instanciarla
El uso de la clase Swift Mirror
funciona si desea extraer el nombre , el valor y el tipo (Swift 3: type(of: value)
, Swift 2: value.dynamicType
) de las propiedades para una instancia de una determinada clase.
Si la clase hereda de NSObject
, puede usar el método class_copyPropertyList
junto con property_getAttributes
para averiguar el nombre y los tipos de propiedades de una clase, sin tener una instancia de ello . Creé un proyecto en Github para esto, pero aquí está el código:
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
}
Donde primitiveDataTypes
es un Diccionario que asigna una letra en la cadena de atributo a un tipo de valor:
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
}
Puede extraer el NSObject.Type
de todas las propiedades que tipo de clase hereda de NSObject
como NSDate
(Swift3: Date
), NSString
(Swift3: String
?) Y NSNumber
, sin embargo, se almacena en el tipo Any
(como se puede ver como Tipo del valor del diccionario devuelto por el método). Esto se debe a las limitaciones de los value types
de value types
, como Int, Int32, Bool. Desde esos tipos no heredan de NSObject, llamando .self
sobre, por ejemplo un Int - Int.self
no vuelve NSObject.Type, sino más bien del tipo Any
. Por lo tanto, el método devuelve Dictionary<String, Any>?
y no Dictionary<String, NSObject.Type>?
.
Puedes usar este método así:
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'
También puede intentar convertir el Any
en NSObject.Type
, que tendrá éxito para todas las propiedades NSObject
de NSObject
, luego puede verificar el tipo utilizando el operador estándar ==
:
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 declara este operador personalizado ==
:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
Incluso puedes verificar el tipo de value types
de value types
como este:
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'")
}
}
}
LIMITACIONES Esta solución no funciona cuando los value types
son opcionales. Si ha declarado una propiedad en su subclase de NSObject como esta: var myOptionalInt: Int?
, el código anterior no encontrará esa propiedad porque el método class_copyPropertyList
no contiene tipos de valor opcionales.