Swift Language
Reflectie
Zoeken…
Syntaxis
- Spiegel (reflecterend: instantie) // Initialiseert een spiegel met het te reflecteren onderwerp
- mirror.displayStyle // Weergavestijl gebruikt voor Xcode-speeltuinen
- mirror.description // Tekstuele weergave van deze instantie, zie CustomStringConvertible
- mirror.subjectType // Retourneert het type van het onderwerp dat wordt weerspiegeld
- mirror.superclassMirror // Retourneert de spiegel van de superklasse van het onderwerp dat wordt weerspiegeld
Opmerkingen
- Algemene opmerkingen:
Een Mirror
is een struct
gebruikt bij de introspectie van een object in Swift. Het meest prominente bezit is de reeks kinderen. Een mogelijke use case is om een struct voor Core Data
te serialiseren. Dit wordt gedaan door een struct
converteren naar een NSManagedObject
.
- Basisgebruik voor spiegelopmerkingen:
De children
eigenschap van een Mirror
is een array van onderliggende objecten van het object de spiegel instantie reflecteert. Een child
object heeft twee eigenschappen label
en value
. Een kind kan bijvoorbeeld een eigenschap zijn met de title
en de waarde van Game of Thrones: A Song of Ice and Fire
.
Basisgebruik voor spiegel
De klasse maken als onderwerp van de spiegel
class Project {
var title: String = ""
var id: Int = 0
var platform: String = ""
var version: Int = 0
var info: String?
}
Een instantie maken die daadwerkelijk het onderwerp van de spiegel zal zijn. Ook hier kunt u waarden toevoegen aan de eigenschappen van de klasse Project.
let sampleProject = Project()
sampleProject.title = "MirrorMirror"
sampleProject.id = 199
sampleProject.platform = "iOS"
sampleProject.version = 2
sampleProject.info = "test app for Reflection"
De onderstaande code toont het maken van de instantie Mirror. De eigenschap AnyForwardCollection<Child>
van de spiegel is een AnyForwardCollection<Child>
waarbij Child
typalias tuple is voor de eigenschap en waarde van het onderwerp. Child
had een label: String
en 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)")
}
Uitvoer in Speeltuin of Console in Xcode voor de for-lus hierboven.
title:MirrorMirror
id:199
platform:iOS
version:2
info:Optional("test app for Reflection")
Getest in Playground op Xcode 8 beta 2
Type en namen van eigenschappen voor een klasse ophalen zonder deze te instantiëren
Het gebruik van de Swift-klasse Mirror
werkt als u naam , waarde en type (Swift 3: type(of: value)
, Swift 2: value.dynamicType
) van eigenschappen voor een instantie van een bepaalde klasse wilt extraheren.
Als uw klasse NSObject
van NSObject
, kunt u de methode class_copyPropertyList
samen met property_getAttributes
gebruiken om de naam en typen eigenschappen voor een klasse te achterhalen - zonder er een instantie van te hebben . Ik heb hiervoor een project op Github gemaakt , maar hier is de 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
}
Waar primitiveDataTypes
een woordenboek is dat een letter in de kenmerkreeks aan een waardetype toewijst:
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
}
Het kan het NSObject.Type
van alle eigenschappen die het klasse-type van NSObject
erft, zoals NSDate
(Swift3: Date
), NSString
(Swift3: String
?) En NSNumber
, maar het wordt opgeslagen in het type Any
(zoals u kunt zien als de type van de waarde van het woordenboek geretourneerd door de methode). Dit komt door de beperkingen van value types
zoals Int, Int32, Bool. Omdat deze typen niet erven van NSObject, .self
op bijvoorbeeld een Int - Int.self
niet NSObject.Type, maar eerder het type Any
. Dus de methode retourneert Dictionary<String, Any>?
en niet Dictionary<String, NSObject.Type>?
.
U kunt deze methode als volgt gebruiken:
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'
Je kunt ook proberen de Any
naar NSObject.Type
te NSObject.Type
, wat zal lukken voor alle eigenschappen die van NSObject
, dan kun je het type controleren met de standaard ==
operator:
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'")
}
}
}
}
Als u deze aangepaste operator ==
declareert:
func ==(rhs: Any, lhs: Any) -> Bool {
let rhsType: String = "\(rhs)"
let lhsType: String = "\(lhs)"
let same = rhsType == lhsType
return same
}
U kunt dan zelfs het type value types
als volgt controleren:
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'")
}
}
}
BEPERKINGEN Deze oplossing werkt niet als value types
optioneel zijn. Als u een eigenschap in uw NSObject-subklasse als volgt hebt aangegeven: var myOptionalInt: Int?
, zal de bovenstaande code die eigenschap niet vinden omdat de methode class_copyPropertyList
geen optionele waardetypen bevat.