注册

Swift 反射,揭开面纱

与iOS Runtime不一样,Swift的反射用了另一套API,实现机制也完全不一样


1. iOS Runtime

  • 其实基于Objc的Runtime是iOS开发的黑魔法,比如神奇的Method Swizzle可以交换任何iOS的系统方法, 再比如消息转发机制,又如class_copyIvarList等方法,可以动态获取一个类裡面所有的方法和属性, 以及动态给一个类新增属性和方法.
  • Objc的Runtime是如此的强大,再加上KVC和KVO这两个利器,可以实现很多你根本就想不到的功能,给iOS开发带来极大的便捷。

2. Apple推出全新的Swift语言后,单纯的Swift型别不再兼容Objc的Runtime,

Swift作为一门静态语言,所有资料的型别都是在编译时就确定好了的,但是Apple为了让Swift相容Objc,让Swift也使用了Runtime。这显然会拖累Swift的执行效率,和Apple所宣称Swift具有超越Objective-C的效能的观点完全不符。而Swift在将来是会慢慢替代 Objective-C的成为iOS或者OSX开发的主流语言,所以为了效能,我们应该尽量使用原生的Swift,避免让Runtime进行Swift型别->Objc型别的隐式转换。
Swift目前只有有限的反射功能,完全不能和Objc的Runtime相比。

首先作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发公众号:编程大鑫,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!

什么是反射

反射是一种计算机处理方式。是程式可以访问、检测和修改它本身状态或行为的一种能力。
上面的话来自百度百科。使用反射有什么用,看一些iOS Runtime的文章应该会很明白。下面再列举一下

  • 动态地建立物件和属性,
  • 动态地获取一个类裡面所有的属性,方法。
  • 获取它的父类,或者是实现了什么样的介面(协议)
  • 获取这些类和属性的访问限制(Public 或者 Private)
  • 动态地获取执行中物件的属性值,同时也能给它赋值(KVC)
  • 动态呼叫例项方法或者类方法
  • 动态的给类新增方法或者属性,还可以交换方法(只限于Objective-C)

上面的一系列功能的细节和计算机语言的不同而不同。对于Objective-C来说,位于中的一系列方法就是完成这些功能的,严格来说Runtime并不是反射。而Swift真正拥有了反射功能,但是功能非常弱,目前只能访问和检测它本身,还不能修改。

Swift的反射

Swift的反射机制是基于一个叫Mirror的Stuct来实现的。具体的操作方式为:首先建立一个你想要反射的类的例项,再传给Mirror的构造器来例项化一个Mirror物件,最后使用这个Mirror来获取你想要的东西。

Mirror结构体常用属性:
subjectType:对象类型
children:反射对象的属性集合
displayStyle:反射对象展示类型

下面来简单介绍下Mirror的使用:

  1. 获取对象类型
  2. 获取一个类的属性名称和属性的值
        let p = Person()
p.name = "刘伟湘"
p.age = 22

let mirror:Mirror = Mirror(reflecting: p)

/*
* 1\. 获取对象类型
*/

print("获取对象类型:\(mirror.subjectType)")
//打印结果: 获取对象类型:Person

/*
* 2\. 获取对象的所有属性名称和属性值
*/

for property in mirror.children {
let propertyNameStr = property.label! // 属性名使用!,因为label是optional类型
let propertyValue = property.value // 属性的值
print("\(propertyNameStr)的值为:\(propertyValue)")
//打印结果: name的值为:Optional("刘伟湘") age的值为:22
}


swfit反射的应用场景现在还比较狭窄,因为功能还不够完善,比较常见的反射应用场景就是自定义类模型转字典

class NewViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

self.navigationItem.title = "new"
self.view.backgroundColor = .white

// 创建一个User实例对象模型
let user1 = User()
user1.name = "刘伟湘"
user1.age = 100
user1.emails = ["506299396.qq.com","111111111111.qq.com"]
let tel1 = Telephone(title: "手机", number: "18711112222")
let tel2 = Telephone(title: "公司座机", number: "2800034")
user1.tels = [tel1, tel2]

// 模型转字典
if let model = user1.toJSONModel() {
print(model)
}
/*
*打印结果
["age": 100, "tels": ["item0": ["number": "18711112222", "title": "手机"], "item1": ["number": "2800034", "title": "公司座机"]], "emails": ["item0": "506299396.qq.com", "item1": "111111111111.qq.com"], "name": "刘伟湘"]
*
*/
}
}

class User {
var name:String = ""
var nickname:String?
var age:Int?
var emails:[String]?
var tels:[Telephone]?
}

// 电话结构体
struct Telephone {
var title:String // 电话标题
var number:String // 电话号码
}

// 自定义一个JSON协议
protocol Sam_JSON {
func toJSONModel() -> Any?
}

// 扩展协议方法,实现一个通用的toJSONModel方法(反射实现)
extension Sam_JSON {
// 将模型数据转成可用的字典数据,Any表示任何类型,除了方法类型
func toJSONModel() -> Any? {
// 根据实例创建反射结构体Mirror
let mirror = Mirror(reflecting: self)

if mirror.children.count > 0 {
// 创建一个空字典,用于后面添加键值对
var result: [String:Any] = [:]

for (idx, children) in mirror.children.enumerated() {
let propertyNameString = children.label ?? "item\(idx)"
let value = children.value
// 判断value的类型是否遵循JSON协议,进行深度递归调用
if let jsonValue = value as? Sam_JSON {
result[propertyNameString] = jsonValue.toJSONModel()
}
}
return result
}
return self
}
}

// 扩展可选类型,使其遵循JSON协议,可选类型值为nil时,不转化进字典中
extension Optional: Sam_JSON {
func toJSONModel() -> Any? {
if let x = self {
if let value = x as? Sam_JSON {
return value.toJSONModel()
}
}
return nil
}
}

// 扩展两个自定义类型,使其遵循JSON协议
extension User: Sam_JSON { }
extension Telephone: Sam_JSON { }

// 扩展Swift的基本数据类型,使其遵循JSON协议
extension String: Sam_JSON { }
extension Int: Sam_JSON { }
extension Bool: Sam_JSON { }
extension Dictionary: Sam_JSON { }
extension Array: Sam_JSON { }


作者:编程大鑫
链接:https://www.jianshu.com/p/9fdb13d62498


0 个评论

要回复文章请先登录注册