Swift Language
Riflessione
Ricerca…
Sintassi
- Specchio (che riflette: istanza) // Inizializza uno specchio con il soggetto da riflettere
- mirror.displayStyle // Visualizza lo stile utilizzato per i campi da gioco Xcode
- mirror.description // Rappresentazione testuale di questa istanza, vedere CustomStringConvertible
- mirror.subjectType // Restituisce il tipo del soggetto che viene riflesso
- mirror.superclassMirror // Restituisce il mirror della super-classe del soggetto che viene riflesso
Osservazioni
- Revisione generale:
Uno Mirror
è una struct
utilizzata nell'introspezione di un oggetto in Swift. La sua proprietà più importante è l'array per bambini. Un possibile caso d'uso è serializzare una struttura per Core Data
. Questo viene fatto convertendo una struct
in un NSManagedObject
.
- Uso di base per i commenti dello specchio:
La proprietà children
di un Mirror
è una matrice di oggetti figlio dall'oggetto che l'istanza Mirror sta riflettendo. Un oggetto child
ha due proprietà label
e value
. Per esempio un bambino potrebbe essere una proprietà con il title
del nome e il valore di Game of Thrones: A Song of Ice and Fire
.
Uso di base per Mirror
Creare la classe per essere il soggetto dello specchio
class Project {
var title: String = ""
var id: Int = 0
var platform: String = ""
var version: Int = 0
var info: String?
}
Creare un'istanza che sarà effettivamente l'oggetto dello specchio. Anche qui è possibile aggiungere valori alle proprietà della classe Project.
let sampleProject = Project()
sampleProject.title = "MirrorMirror"
sampleProject.id = 199
sampleProject.platform = "iOS"
sampleProject.version = 2
sampleProject.info = "test app for Reflection"
Il codice seguente mostra la creazione dell'istanza Mirror. La proprietà child del mirror è AnyForwardCollection<Child>
dove Child
è una tupla di tipografia per la proprietà e il valore dell'oggetto. Child
aveva label: String
e 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)")
}
Uscita in Playground o Console in Xcode per il ciclo for sopra.
title:MirrorMirror
id:199
platform:iOS
version:2
info:Optional("test app for Reflection")
Testato in Playground su Xcode 8 beta 2
Ottenere il tipo e i nomi delle proprietà per una classe senza dover istanziarla
Usando la classe Swift Mirror
funziona se si desidera estrarre nome , valore e tipo (Swift 3: type(of: value)
, Swift 2: value.dynamicType
) di proprietà per un'istanza di una determinata classe.
Se la classe eredita da NSObject
, è possibile utilizzare il metodo class_copyPropertyList
insieme a property_getAttributes
per trovare il nome e i tipi di proprietà per una classe, senza avere un'istanza di essa . Ho creato un progetto su Github per questo, ma ecco il codice:
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
}
Dove primitiveDataTypes
è un dizionario che associa una lettera nella stringa di attributo a un tipo di valore:
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
}
Può estrarre il NSObject.Type
di tutte le proprietà che il tipo di classe eredita da NSObject
come NSDate
(Swift3: Date
), NSString
(Swift3: String
?) E NSNumber
, tuttavia è memorizzato nel tipo Any
(come puoi vedere come tipo del valore del dizionario restituito dal metodo). Ciò è dovuto alle limitazioni dei value types
di value types
come Int, Int32, Bool. Poiché questi tipi non ereditano da NSObject, chiamando .self
su es. Int - Int.self
non restituisce NSObject.Type, ma il tipo Any
. Quindi il metodo restituisce Dictionary<String, Any>?
e non il Dictionary<String, NSObject.Type>?
.
Puoi usare questo metodo in questo modo:
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'
Puoi anche provare a lanciare Any
to NSObject.Type
, che avrà successo per tutte le proprietà che ereditano da NSObject
, quindi puoi controllare il tipo usando l'operatore 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'")
}
}
}
}
Se dichiari questo operatore ==
personalizzato:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
È quindi possibile controllare anche il tipo di value types
di value types
come questo:
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'")
}
}
}
LIMITAZIONI Questa soluzione non funziona quando i value types
sono opzionali. Se hai dichiarato una proprietà nella sottoclasse NSObject come questa: var myOptionalInt: Int?
, il codice sopra non troverà quella proprietà perché il metodo class_copyPropertyList
non contiene tipi di valore opzionali.