反射 Swift.Reflection 就是可以动态获取类型、成员信息,在运行时可以调用属性、方法等行为的特性
KeyPath
也称 WritableKeyPath 添加引用标识键路径的功能,该路径指的是应用它的整个输入值。类似 OC 中的 KVC
let keyPath = \Int.self
print(keyPath) // Swift.WritableKeyPath<Swift.Int, Swift.Int>
var a = 10
print(a) // 10
a[keyPath: keyPath] = 20
print(a) // 20
print(a[keyPath: keyPath]) // 20
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let man = Person(name: "huangjian", age: 20)
let nameKeyPath: KeyPath<Person, String> = \Person.name // Swift.ReferenceWritableKeyPath<swift_demo.Person, Swift.String>
let nameCountKeyPath = \Person.name.count // Swift.KeyPath<swift_demo.Person, Swift.Int>
man[keyPath: nameKeyPath] = "mr.huangjian"
print(man[keyPath: nameKeyPath]) // "mr.huangjian"
print(man[keyPath: nameCountKeyPath]) // 12
KVC / KVO
Swift 中可以使用 KVC \ KVO ,但有两个基本条件:
- 使用 @objc 修饰对应的属性
- 属性所在的类、监听器继承自 NSObject
此外:
- KVO 监听的属性还需添加 dynamic,即 @objc dynamic
- KVC 键值编码的属性可以为可选类型,在该可选类型可以在 OC 中表示时
- KVC 可以通过 #keyPath 获取类的属性名称
class Observer: NSObject {
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
if let newValue = change?[.newKey] {
print("[observe]: ", newValue)
}
}
}
class Person: NSObject {
@objc dynamic var age: Int = 0
var observer = Observer()
override init() {
super.init()
self.addObserver(observer, forKeyPath: "age", options: .new, context: nil)
}
deinit {
self.removeObserver(observer, forKeyPath: "age")
}
}
let person = Person()
person.age = 20 // [observe]: 20
person.age = 30 // [observe]: 30
class Movie: NSObject {
@objc dynamic var name: String = ""
var observation: NSKeyValueObservation?
override init() {
super.init()
// 必须接收返回值,不接收就不会收到监听结果...
observation = observe(\Movie.name, options: .new) { (movie, change) in
if let newValue = change.newValue {
print(newValue)
}
}
}
}
let movie = Movie()
movie.name = "Home Alone"
movie.name = "Home Alone 2nd."
movie.setValue("Home Alone 3rd.", forKey: "name")
import Foundation
class MyClass: NSObject {
/**
若 prop1 的数据类型改为 `Int?` 则会报错:
属性不能标记为@objc,因为它的类型不能在Objective-C中表示
Property cannot be marked @objc because its type cannot be represented in Objective-C
可以理解为 Int(int, NSInteger) 无法为 nil,而 String(NSString) 可以为 nil.
*/
@objc var prop1: String?
@objc var prop2: Int = 0
var prop3: Bool = true
}
let obj = MyClass()
obj.prop2 = 10
obj.setValue("hj", forKey: "prop1")
let v1 = obj.value(forKey: "prop1")
print(v1 as Any) // Optional(hj)
import Foundation
class Person: NSObject {
@objc var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
print(#keyPath(Person.name)) // "name"
添加存储属性
默认情况下 extension 不可以新增存储属性,而借助关联对象却可以
class Person {}
extension Person {
private static var _ageKey: Void?
var age: Int {
set {
objc_setAssociatedObject(self, &Self._ageKey,
newValue, .OBJC_ASSOCIATION_ASSIGN)
}
get {
return objc_getAssociatedObject(self, &Self._ageKey) as? Int ?? 0
}
}
}
let p = Person()
print(p.age) // 0
p.age = 10
print(p.age) // 10
指针类型
Swift 中也有专门的指针类型,这些都被定性为 Unsafe (不安全的),常见的有以下4种类型:
- UnsafePointer
类似于 const Pointee * - UnsafeMutablePointer
类似于 Pointee * - UnsafeRawPointer 类似于 const void *
- UnsafeMutableRawPointer 类似于 void *
func test(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 10
}
var age = 10
test(&age)
print(age) // 20
获取对象的内存地址:
func getObjAddress(_ object: AnyObject) -> Any {
return Unmanaged.passUnretained(object).toOpaque()
}
Mirror
Swift 的反射机制是基于一个叫 Mirror 的 struct 来实现的,其内部有如下属性和方法:
children: Mirror.Children // 实例的子节点(属性)
displayStyle: Mirror.DisplayStyle? // 实例的展示风格(eg. class/struct/enum/array...)
subjectType: Any.Type // 实例的类型
superclassMirror() -> Mirror? // 实例父类的 mirror
案例1:
class User {
var name: String = ""
var nickname: String?
var age: Int?
var emails: [String]?
}
let user = User()
user.name = "huangjian"
user.age = 20
user.emails = ["mr.huangjian@foxmail.com", "1776549643@qq.com"]
let mirror = Mirror(reflecting: user)
print(mirror.subjectType) // User
print(mirror.displayStyle!) // class
print(mirror.children.count) // 4
for case let (label?, value) in mirror.children {
print("\(label): \(type(of: value)) = \(value);")
}
案例2:解包,不会有编译器的警告
func unwrap(_ any: Any) -> Any {
let mirror = Mirror(reflecting: any)
if mirror.displayStyle != .optional {
return any // Non optional
}
if mirror.children.count == 0 {
return any // nil
}
return mirror.children.first!.value // optional
}
do {
let name: String? = nil
print(unwrap(name as Any)) // nil
}
do {
let name: String? = "hj"
print(unwrap(name as Any)) // hj
}
do {
let name: String = "hj"
print(unwrap(name)) // hj
}
[备注]:使用反射将自定义对象数据序列化成JSON数据(字符串) Swift 进阶