反射 Mirror | Swift 动态性
admin
2024-01-28 02:57:38
0

Mirror是Swift中的反射机制,反射就是可以动态的获取类型以及成员信息,同时也可以在运行时动态的调用方法和属性等。

1. Mirror 简介

Mirror是Swift中的反射机制的实现,它的本质是一个结构体。
创建 Mirror 最简单的方式就是使用 reflecting 构造器:

public init(reflecting subject: Any)

正如你所见,对象的类型是 Any。这是 Swift 中最通用的类型。Swift 中的任何东西至少都是 Any类型的。这样一来 mirror 就可以兼容 structclassenumTupleArrayDictionaryset 等。

下面列举了 Mirror 可用的属性 / 方法:

  • let children: Children:对象的子节点。
  • displayStyle: Mirror.DisplayStyle?:对象的展示类型
  • let subjectType: Any.Type:对象的类型
  • func superclassMirror() -> Mirror?:对象父类的 mirror

displayStyle

public enum DisplayStyle {case Structcase Classcase Enumcase Tuplecase Optionalcase Collectioncase Dictionarycase Set
}

它会返回 DisplayStyle enum 的其中一种情况。如果你想要对某种不支持的类型进行反射,你会得到一个空的 Optional 值。
例如:
正如之前我们知道的,反射只要求对象是 Any 类型,而且Swift 标准库中还有很多类型为 Any 的东西没有被列举在上面的 DisplayStyle enum 中。如果试图反射它们中间的某一个又会发生什么呢?比如 closure。

let closure = { (a: Int) -> Int in return a * 2 }
let aMirror = Mirror(reflecting: closure)
print("\(aMirror)----------\(aMirror.displayStyle)")
//Mirror for (Int) -> Int----------nil

children

这会返回一个包含了对象所有的子节点的 AnyForwardCollection。这些子节点不单单限于 Array 或者 Dictionary 中的条目。诸如 struct 或者 class 中所有的属性也是由 AnyForwardCollection 这个属性返回的子节点。AnyForwardCollection 协议意味着这是一个支持遍历的 Collection 类型。

public class Store {let storesToDisk: Bool = true
}
public class BookmarkStore: Store {let itemCount: Int = 10
}
public struct Bookmark {enum Group {case Techcase News}private let store = {return BookmarkStore()}()let title: String?let url: NSURLlet keywords: [String]let group: Group
}
let aBookmark = Bookmark(title: "Appventure", url: NSURL(string: "appventure.me")!, keywords: ["Swift", "iOS", "OSX"], group: .Tech)let aMirror = Mirror(reflecting: aBookmark)
for case let (label?, value) in aMirror.children {print (label, value)
}
//输出:
//store TestDemo3.ViewController.BookmarkStore
//title Optional("Appventure")
//url appventure.me
//keywords ["Swift", "iOS", "OSX"]
//group Tech

SubjectType

这是对象的类型:

print(aMirror.subjectType)
//输出 : Bookmark
print(Mirror(reflecting: 5).subjectType)
//输出 : Int
print(Mirror(reflecting: "test").subjectType)
//输出 : String
print(Mirror(reflecting: NSNull()).subjectType)
//输出 : NSNull

SuperclassMirror

这是我们对象父类的 mirror。如果这个对象不是一个类,它会是一个空的 Optional 值。如果对象的类型是基于类的,你会得到一个新的 Mirror:

// 试试 struct
print(Mirror(reflecting: aBookmark).superclassMirror)
// 输出: nil
// 试试 class
print(Mirror(reflecting: aBookmark.store).superclassMirror)
// 输出: Optional(Mirror for Store)

2.反射 Mirror 使用

1.Mirror 转换对象为字典

  • 定义如下对象:

    struct Person {var name: String = "YDW"var isMale: Bool = truevar birthday: Date = Date()}class Animal: NSObject {private var eat: String = "吃"var age: Int = 0var optionValue: String?}class Cat: Animal {var like: [String] = ["mouse", "fish"]var master = Person()}
  • 遍历出字典:
   func mapDic(mirror: Mirror) -> [String: Any] {var dic: [String: Any] = [:]for child in mirror.children {// 如果没有label就会被抛弃if let label = child.label {let propertyMirror = Mirror(reflecting: child.value)dic[label] = child.value}}// 添加父类属性if let superMirror = mirror.superclassMirror {let superDic = mapDic(mirror: superMirror)for p in superDic {dic[p.key] = p.value}}return dic}
  • 使用 Mirror 转换对象并打印结果, 可以看到可以打印出私有属性:
// Mirror使用let objc  = Cat()let  mirror = Mirror(reflecting: objc)let mirrorDic = mapDic(mirror: mirror)print(mirrorDic)// 打印结果
["master": TestDemo3.ViewController.Person(name: "YDW", isMale: true, birthday: 2022-07-15 03:51:59 +0000), "eat": "吃", "optionValue": nil, "age": 0, "like": ["mouse", "fish"]]
  • 在实际运用中,可以将应用于元组参数传递(比如网络请求传参,传入元组,网络请求时转换为字典),优点:外部使用知道具体传入什么参数,参数更改不影响方法错误。
// 外部参数定义var params = (title: "name", comment: "Mirror")// 网络层统一转换为字典,进行网路请求let paramsDic = mapDic(mirror: Mirror(reflecting: params))print(paramsDic)// 打印结果["title": "name", "comment": "Mirror"]
  • 需要注意是只能传入基本类型,并且元组参数要命名,如果直接使用(“name”,“Mirror”)则会变成下面这种情况:
// 外部参数定义var params = ("name","Mirror")// 网络层统一转换为字典,进行网路请求let paramsDic = mapDic(mirror: Mirror(reflecting: params))print(paramsDic)// 打印[".1": "Mirror", ".0": "name"]
  1. JSON 解析
  • 定义一个 YDWTeacher 类:
class YDWTeacher {var age = 18var name = "YDW"
}
  • 运用 Mirror 来解析,实现代码如下:
// JSON解析func test(_ obj: Any) -> Any {let mirror = Mirror(reflecting: obj)// 判断条件 - 递归终止条件guard !mirror.children.isEmpty else {return obj}// 字典var keyValue: [String: Any] = [:]// 遍历for children in mirror.children {if let keyName = children.label {// 递归调用keyValue[keyName] = test(children.value)} else {print("children.label 为空")}}return keyValue}// 使用var t = YDWTeacher()print(test(t))
  • 运行代码,打印结果如下:
["name": "YDW","age": 18]
  • 如果想在工程中大量的使用上述的 JSON 解析,可以将 Mirror JSON 解析抽取成一个协议,然后提供一个默认实现,让类遵守协议,如下:
// 定义JSON解析协议protocol CustomJSONMap {func jsonMap() -> Any}// 提供默认实现extension CustomJSONMap{func jsonMap() -> Any{let mirror = Mirror(reflecting: self)// 递归终止条件guard !mirror.children.isEmpty else {return self}// 字典,用于存储json数据var keyValue: [String: Any] = [:]// 遍历for children in mirror.children {if let value = children.value as? CustomJSONMap {if let keyName = children.label {// 递归keyValue[keyName] = value.jsonMap()} else {print("key是nil")}} else {print("当前-\(children.value)-没有遵守协议")}}return keyValue}}// 让类遵守协议(注意:类中属性的类型也需要遵守协议,否则无法解析)class YDWTeacher: CustomJSONMap {var age = 18var name = "YDW"}// 使用var t = YDWTeacher()print(t.jsonMap())
  • 运行,可以发现并没达到我们预想的结果,这是因为 YDWTeacher 的属性的类型也需要遵守 CustomJSONMap 协议:
当前-18-没有遵守协议
当前-YDW-没有遵守协议
  • 修改如下,这样就可以达到我们预想的结果:
// Int、String遵守协议
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}// 打印结果 
["name": "YDW", "age": 18]

3.错误处理

  • 为了让封装的JSON 解析更加完善,除了对正常返回的处理,还需要对其中的错误进行处理,在上面的封装中,采用的是 print 打印的,这样并不规范,也不好维护及管理。那么如何在 swift 中正确的表达错误呢?
  • Swift 中,提供了 Error 协议来标识当前应用程序发生错误的情况,其中 Error 的定义如下:
public protocol Error { }
  • 可以看到:Error 是一个空协议,其中没有任何实现,这也就意味着可以遵守该协议,然后自定义错误类型,因此不管是 struct、Class、enum,都可以遵循这个 Error 来表示一个错误。
  • 那么上面封装的 JSON 解析修改错误处理,首先定义一个 JSONMapError 错误枚举,将默认实现的 print 替换成枚举类型:
// 定义错误类型
enum JSONMapError: Error{case emptyKeycase notConformProtocol
}
extension CustomJSONMap {func jsonMap() throws -> Any {let mirror = Mirror(reflecting: self)// 递归终止条件guard !mirror.children.isEmpty else {return self}// 字典,用于存储json数据var keyValue: [String: Any] = [:]// 遍历for children in mirror.children {if let value = children.value as? CustomJSONMap {if let keyName = children.label {// 递归keyValue[keyName] = try value.jsonMap()} else {throw JSONMapError.emptyKey}} else {throw JSONMapError.notConformProtocol}}return keyValue}}
  • 封装完成,使用如下:
// 使用时需要加上try
var t = YDWTeacher()
do{_ = try t.jsonMap()
}catch JSONMapError.emptyKey{print("???emptyKey????")
}catch JSONMapError.notConformProtocol{print("???notConformProtocol?????")
}catch{
}

4.获取类型,属性个数及其值

  • 定义一个用户类:
class YDWUser {var name: String = ""var nickName: String?var age: Int?var emails: [String]?
}
  • 然后创建一个用户对象,并通过反射获取这个对象的信息:
// 创建一个User实例对象let user = YDWUser()user.name = "DW"user.age = 18user.emails = ["12345@qq.com", "56789@qq.com"]// 将user对象进行反射let hMirror = Mirror(reflecting: user)print("对象类型:\(hMirror.subjectType)")print("对象子元素个数:\(hMirror.children.count)")print("--- 对象子元素的属性名和属性值分别如下 ---")for case let  (label?, value)  in  hMirror.children {print("属性:\(label)     值:\(value)")}
  • 打印如下:
对象类型:YDWUser
对象子元素个数:4
--- 对象子元素的属性名和属性值分别如下 ---
属性:name     值:DW
属性:nickName     值:nil
属性:age     值:Optional(18)
属性:emails     值:Optional(["12345@qq.com", "56789@qq.com"])

5.通过属性名(字符串)获取对应的属性值,并对值做类型判断(包括是否为空)

  • 定义两个方法:getValueByKey() 是用来根据传入的属性名字符串来获取对象中对应的属性值,unwrap() 是用来给可选类型拆包的(对于非可选类型则返回原值):
// 根据属性名字符串获取属性值func getValueByKey(obj: AnyObject, key: String) -> Any {let hMirror = Mirror (reflecting: obj)for case let (label?, value) in hMirror.children {if label == key {return unwrap(any: value)}}return  NSNull ()}// 将可选类型(Optional)拆包func unwrap(any: Any ) ->  Any  {let mi =  Mirror (reflecting: any)if mi.displayStyle! != .optional {return any}if mi.children.count == 0 {return any}let  (_, some) = mi.children.first!return  some}
  • 用上例的 YDWUser 对象做测试:
// 创建一个User实例对象let user = YDWUser()user.name = "DW"user.age = 18user.emails = ["12345@qq.com", "56789@qq.com"]// 通过属性名字符串获取对应的值let name = getValueByKey(obj: user, key:  "name")let nickName = getValueByKey(obj: user, key:  "nickName")let age = getValueByKey(obj: user, key:  "age")let emails = getValueByKey(obj: user, key:  "emails")let tel = getValueByKey(obj: user, key:  "tel")print(name, nickName, age, emails, tel)// 对于获取到的值进行类型判断if name is NSNull {print("name这个属性不存在")} else if (name as? AnyObject) == nil {print("name这个属性是个可选类型,且为nil")} else if name is String {print("name这个属性String类型,其值为:\(name)")}if tel is NSNull {print("tel这个属性不存在")} else if (tel as? AnyObject) == nil {print("tel这个属性是个可选类型,且为nil")} else if tel is String {print("tel这个属性String类型,其值为:\(tel)")}
  • 运行程序,打印如下:

DW nil 18 ["12345@qq.com", "56789@qq.com"] 
name这个属性String类型,其值为:DW
tel这个属性不存在

3.Swift动态性

  • 所谓动态:就是在运行阶段才知道自己的类是什么
  • 父类对象指向子类对象,是动态特性,因为在运行的时候,才知道这个变量真正的类型是子类
  • 具有方法动态派发(函数表派发,消息派发)
  • 继承自OC的类Swift类,具有动态特性
  • dynamic修饰的方法和属性,具有动态特性
  • Swift中的反射机制,是动态特性

静态派发
在编译期的时候,编译器就知道要为某个方法调用某种实现

动态派发
对于每个方法的调用,编译器必须在方法列表中查找执行方法的实现,比如在运行时判断是选择父类的实现,还是子类的实现。对于对象的内存都是在运行时分配的,因此只能在运行时执行检查

编译型语言的函数派发方式

  • 直接派发(Direct Dispatch)
    编译后确定了方法的调用地址(静态派发),汇编代码中,直接跳到了方法的地址执行,生成的汇编指令最少,速度最快
    例如C语言,C++默认也是直接派发
    由于缺乏动态性,无法实现多态
  • 函数表派发(Table Dispatch)
    在运行时通过函数查找需要执行的方法,多一次查表的过程,速度比直接派发慢
    C++的虚函数(Virtual Table),维护一个虚函数表,对象创建的时候会保存虚表的指针,调用方之前,会从对象中取出虚表地址,根据编译时的方法偏移量从虚表取出方法的地址,跳到方法的地址执行
  • 消息派发(Message Dispatch)
    objc消息发送机制
  • 性能:直接派发 > 函数表派发 > 消息机制派发

直接派发

  • 全局函数
  • 使用static声明的方法
  • 使用final声明的所有方法,使用final声明的类里面的所有方法
  • 使用private声明的方法和属性,会隐式final声明
  • 值类型的方法,比如structenum中的方法
  • extension中没有使用@objc修饰的实例方法

函数表派发

  • 只有引用类型才是函数表派发
  • 在Swift中,类的方法默认使用函数表派发的方式
  • Swift对于协议Protocol默认使用的是函数表派发,协议可以为struct提供多态的支持
  • Swift的函数表叫做 witness table(C++叫做virtual table)
    1.每个子类都有自己的表结构
    2.对于类中每个重写的方法,都有不同的函数指针
    3.当子类添加新方法时,这些方法指针会添加到函数表的末尾
    4.在运行时使用此表来调用函数的实现 (函数表指针 + 函数偏移 就能找到对应的方法)

消息派发

  • dynamic修饰的方法是消息派发
  • 对于继承自NSObject的Swift类,消息派发方式
    注意: @objc修饰方法,只是把方法暴露给objc,是函数表派发

例子

  • 普通的实例方法,使用函数表派发
  • @objc声明的方法,使用函数表派发
  • 对于重写了OC的方法。使用消息派发
  • extension中的方法,直接派发
  • dynamic修饰的方法,使用消息派发

//举例说明:
class CustomView: UIView {/// static修饰:直接派发static func staticMehod() {}/// private: 直接派发private func privateMethod() {}/// final修饰:直接派发final func finalMethod() {}/// 直接派发static func staticMethod() {}/// 普通的实例方法 函数表派发func commonMethod() {}/// @objc修饰 函数表派发@objc func method1() {}/// dynamic修饰: 消息派发@objc dynamicfunc method2() {}/// 重写了OC的方法: 消息派发override func layoutSubviews() {super.layoutSubviews()}
}

相关内容

热门资讯

除了三界内、六道内,肯定有些众... 除了三界内、六道内,肯定有些众生/灵魂在此之外。是什么情况?哪本典籍有记载?你的意识消亡后,你不在是...
大家知道天天向上2010***... 大家知道天天向上2010****期里面请了一个四川的通俗音乐老师陶辚竹梳的那个头发,她烫发选的是什么...
明代的科举制度是怎样的? 明代的科举制度是怎样的?明代,生员参加每三年一次在省会举行的乡试,考中的称举人;举人参加每三年一次(...
婚后恋爱的言情小说 婚后恋爱的言情小说不懂说将来,艾米写得很好看。
要如何查有没有人偷我家的电? 要如何查有没有人偷我家的电?把自己家里的所有电器全部关闭,看看是否自己的电表是否还在运行,如果你的电...
用微笑掩饰,悲伤会没有吗? 用微笑掩饰,悲伤会没有吗?不会变,但是至少不会带给别人,你控制住了他的蔓延,当你知道自己没有让自己的...
后羿和杨戬哪个厉害,嫦娥更喜欢... 后羿和杨戬哪个厉害,嫦娥更喜欢谁,那些骗分的别回答你们说的都是废话,我不喜欢听废话嫦娥到底喜欢谁是她...
香辣土豆片的美味 在忙碌的生活中,我们总是渴望找到那些简单又美味的家常菜,既能满足味蕾,又能节省时间。今天,我要和大家...
原汁原味安徽菜!香菇鸡翅根,鲜... 安徽风味的香菇鸡翅根是一道家常又下饭的菜,鸡肉鲜嫩,香菇吸饱了汤汁,香味浓郁,吃起来特别入味。这道菜...
原创 晚... 一、夜晚饮食的特殊性 夜晚对于人体来说是一个特殊的时段,身体在经过一天的活动后,逐渐进入休息和修复的...
原创 老... 在乡村的怀抱中,美食不仅是味蕾的盛宴,更是心灵的慰藉。今天,我有幸为我的丈夫准备了一顿丰盛的晚餐,共...
8道早餐美食,让家人拥有好脾胃... 8道早餐美食,让家人拥有好脾胃口~ 一日之计在于晨,而早餐则是开启活力满满的一天的关键 “钥匙”;一...
原创 咖... 说到日本美食,很多人想到的是寿司、拉面、寿喜锅等食物。 但是如果你在日式料理店里,也能找到法式、意...
叶无心端木孤辰是哪部小说里的 叶无心端木孤辰是哪部小说里的《 穿越,第九个王妃 》,雪色水晶写的。
杨宗保死而复生是穆桂英挂帅第几... 杨宗保死而复生是穆桂英挂帅第几集38集死而复生
蒙太奇是什么意思?有哪些种类? 蒙太奇是什么意思?有哪些种类?RT蒙太奇(法语:Montage)是音译的外来语,原为建筑学术语,意为...
4月23日,是世界读书日,班里... 4月23日,是世界读书日,班里准备在 这天下午3点,在教室开展关于读书的主题班会。作为活动组织者,你...