反射(Reflection)介绍

对于C#Java开发人员来说,肯定都对反射这个概念相当熟悉。所谓反射就是可以动态获取类型、成员信息,同时在运行时(而非编译时)可以动态调用任意方法、属性等行为的特性。

Java上的两个知名框架(hibernatespring)为例。hibernate的属性映射就是通过反射来赋值的,springbean的创建就是根据配置的class来反射构建的。

Objective-C 的 Runtime
在使用ObjC开发时很少强调其反射概念,因为ObjCRuntime要比其他语言中的反射强大的多。在ObjC中可以很简单的实现字符串和类型的转换(NSClassFromString()),实现动态方法调用(performSelector: withObject:),动态赋值(KVC)等等。

Swift中的反射

Swift中并不提倡使用Runtime,而是像其他语言一样使用反射(Reflect)。当然,目前Swift中的反射还没有其他语言中的反射功能强大,不仅远不及OC的Runtime,离Java的反射也有一定的距离。
Swift的反射机制是基于一个叫 Mirror 的 struct 来实现的,其内部有如下属性和方法:

/// 属性 Mirror.Children (label: String?, value: Any)
let children: Mirror.Children
/// 自定义反射
var customMirror: Mirror
/// 反射描述 一般为 Mirror for 类型
var description: String
/// 显示类型,基本类型为nil 枚举值: class, enum , struce, dictionary, array, set, tuple
let displayStyle: Mirror.DisplayStyle?
/// 类型
let subjectType: Any.Type
/// 父类反射, 没有父类为nil
var superclassMirror: Mirror?

通常获取属性一般遍历children来获取。

Swift反射的使用样例

转换对象为字典

struct Person {var name: String = "张三"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 {// 如果没有labe就会被抛弃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使用
let objc  = Cat()
let  mirror = Mirror(reflecting: objc)
let mirrorDic = mapDic(mirror: mirror)
print(mirrorDic)//打印结果
["like": ["mouse", "fish"], "optionValue": nil, "eat": "吃什么", "age": 0, "master": __lldb_expr_48.Person(name: "张三", isMale: true, birthday: 2020-01-02 11:24:30 +0000)]

在实际运用中,可以将应用于元组参数传递(比如网路请求传参,传入元组,网络请求时转换为字典),优点:外部使用知道具体传入什么参数,参数更改不影响方法错误。

// 外部参数定义
var netParams = (title: "标题", comment: "评论,五星好评")// 网络层统一转换为字典,进行网路请求
let parmsDic = mapDic(mirror: Mirror(reflecting: netParams))
print(parmsDic)// 打印结果
["title": "标题", "comment": "评论,五星好评"]

但是需要注意,只能传入基本类型。且元组参数要命名,如果直接使用("标题","评论,五星好评")则会变成下面这种情况。

// 外部参数定义
var netParams = ("标题","评论,五星好评")//(title: "标题", comment: "评论,五星好评")// 网络层统一转换为字典,进行网路请求
let parmsDic = mapDic(mirror: Mirror(reflecting: netParams))
print(parmsDic)
// 打印
[".1": "评论,五星好评", ".0": "标题"]

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

首先定义一个用户类:

1
2
3
4
5
6
7
//用户类
class User {
    var name:String ""  //姓名
    var nickname:String?  //昵称
    var age:Int?   //年龄
    var emails:[String]?  //邮件地址
}

接着创建一个用户对象,并通过反射获取这个对象的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建一个User实例对象
let user1 = User()
user1.name = "hangge"
user1.age = 100
user1.emails = ["hangge@hangge.com","system@hangge.com"]
         
//将user对象进行反射
let hMirror = Mirror(reflecting: user1)
         
print("对象类型:\(hMirror.subjectType)")
print("对象子元素个数:\(hMirror.children.count)")
         
print("--- 对象子元素的属性名和属性值分别如下 ---")
for case let (label?, value) in hMirror.children {
    print("属性:\(label)     值:\(value)")
}

控制台输出信息如下:

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

首先为方便使用,这里定义两个方法。getValueByKey()是用来根据传入的属性名字符串来获取对象中对应的属性值。unwrap()是用来给可选类型拆包的(对于非可选类型则返回原值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//根据属性名字符串获取属性值
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(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
}

下面是实际测试样例,同样用上例的User对象做测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//创建一个User实例对象
let user1 = User()
user1.name = "hangge"
user1.age = 100
user1.emails = ["hangge@hangge.com","system@hangge.com"]
//通过属性名字符串获取对应的值
let name = getValueByKey(user1, key: "name")
let nickname = getValueByKey(user1, key: "nickname")
let age = getValueByKey(user1, key: "age")
let emails = getValueByKey(user1, key: "emails")
let tel = getValueByKey(user1, key: "tel")
print(name, nickname, age, emails, tel)
//当然对于获取到的值也可以进行类型判断
if name is NSNull {
    print("name这个属性不存在")
}else if (name asAnyObject) == nil {
    print("name这个属性是个可选类型,且为nil")
}else if name is String {
    print("name这个属性String类型,其值为:\(name)")
}
if nickname is NSNull {
    print("nickname这个属性不存在")
}else if (nickname asAnyObject) == nil {
    print("nickname这个属性是个可选类型,且为nil")
}else if nickname is String {
    print("nickname这个属性String类型,其值为:\(nickname)")
}
if tel is NSNull {
    print("tel这个属性不存在")
}else if (tel asAnyObject) == nil {
    print("tel这个属性是个可选类型,且为nil")
}else if tel is String {
    print("tel这个属性String类型,其值为:\(tel)")
}

控制台输出信息如下:

通过KVC访问属性值

KVCkey-value coding的缩写。它是一种间接访问对象的机制。其本质是依据OCRuntime的强大动态能力来实现的。在Swift中,只要类继承NSObject即可使用KVC。(有一个叫KVO的,它又是基于KVC,大家有兴趣的可以自行研究下。)
KVC中:key的值就是属性名称的字符串,返回的value是任意类型,需要自己转化为需要的类型。

(注意:正由于KVC是基于Objective-C的,所以其不支持可选类型(optional)的属性,比如上例的 var age:Int? 
因此用户类做如下改造:)

1
2
3
4
5
6
7
//用户类
class UserNSObject{
    var name:String ""  //姓名
    var nickname:String?  //昵称
    var age:Int = 0  //年龄
    var emails:[String]?  //邮件地址
}

KVC主要就是两个方法:
(1)通过key获得对应的属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//创建一个User实例对象
let user1 = User()
user1.name = "hangge"
user1.age = 100
user1.emails = ["hangge@hangge.com","system@hangge.com"]
//使用KVC取值
let name = user1.valueForKey("name")
let nickname = user1.valueForKey("nickname")
let age = user1.valueForKey("age")
let emails = user1.valueForKey("emails")
//let tel = user1.valueForKey("tel")
print(name, nickname, age, emails)
         
//当然对于获取到的值也可以进行类型判断
if name == nil {
    print("name这个属性是个可选类型,且为nil")
}else if name is String {
    print("name这个属性String类型,其值为:\(name)")
}
       
if nickname == nil {
    print("nickname这个属性是个可选类型,且为nil")
}else if nickname is String {
    print("nickname这个属性String类型,其值为:\(nickname)")
}

(2)通过key设置对应的属性值
1
2
3
4
5
6
7
8
9
//创建一个User实例对象
let user1 = User()
//使用KVC赋值
user1.setValue("hangge", forKey: "name")
user1.setValue(100, forKey: "age")
user1.setValue(["hangge@hangge.com","system@hangge.com"], forKey: "emails")
         
print(user1.name, user1.nickname, user1.age, user1.emails)

Swift 反射Mirror的使用相关推荐

  1. Swift之深入解析反射Mirror与错误处理

    一.反射 Mirror 简介 反射是指可以动态获取类型.成员信息,同时在运行时(而非编译时)可以动态调用任意方法.属性等行为的特性. 在使用 OC 开发时很少强调其反射概念,因为 OC 的 runti ...

  2. 反射 Mirror | Swift 动态性

    Mirror是Swift中的反射机制,反射就是可以动态的获取类型以及成员信息,同时也可以在运行时动态的调用方法和属性等. 1. Mirror 简介 Mirror是Swift中的反射机制的实现,它的本质 ...

  3. JAVA中的isMirror函数_Swift中的反射Mirror

    Swift中的反射Mirror [TOC] 前言 Mirror是Swift中的反射机制,对于C#和Java开发人员来说,应该很熟悉反射这个概念.反射就是可以动态的获取类型以及成员信息,同时也可以在运行 ...

  4. Swift之深入解析反射Mirror的底层原理

    一.Mirror 的底层分析 ① 反射 API 反射 API 由两部分实现: 一部分是通过 Swift 实现,即 ReflectionMirror.swift: 一部分是通过 C++ 实现,即 Ref ...

  5. Swift反射API及其用法

    尽管 Swift 一直在强调强类型.编译时安全和静态调度,但它的标准库仍然提供了反射机制.可能你已经在很多博客文章或者类似Tuples.Midi Packets和Core Data的项目中见过它.也许 ...

  6. Swift 反射 API 及用法

    作者:Benedikt Terhechte,原文链接,原文日期:2015-10-24 译者:mmoaay:校对:千叶知风:定稿:千叶知风 尽管 Swift 一直在强调强类型.编译时安全和静态调度,但它 ...

  7. swift之字典转模型kvc、mjextention桥接、反射、HandyJSON、ObjectMapper、Codable

    参考swift4.0字典转模型:https://www.cnblogs.com/shaoting/p/8087153.html =====================kvc字典转模型======= ...

  8. HandyJSON:Swift语言JSON转Model工具库

    背景 JSON是移动端开发常用的应用层数据交换协议.最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上. 但在编程的时候,处 ...

  9. swift enum高级用法

    本文是一篇详细且具有实战意义的教程,涵盖几乎所有枚举(Enum)知识点,为你解答Swift中枚举的应用场合以及使用方法. 和switch语句类似,Swift中的枚举乍看之下更像是C语言中枚举的进阶版本 ...

最新文章

  1. 疫情之下的网站优化该怎样进行?
  2. hive使用derby的服务模式(可以远程模式)
  3. 构造、拷贝构造、赋值、析构
  4. 阿里云上JDK安全证书的实际位置
  5. mysql 5.5 重新编译_源码编译mysql5.5过程记录
  6. Windows XP和Windows 7双系统安装和启动菜单修复
  7. Hive数据如何同步到MaxCompute之实践讲解
  8. apache目录 vscode_VsCode搭建Java开发环境(Spring Boot项目创建、运行、调试)
  9. 视频PPT互动问答丨Oracle Groundbreak亚太巡演2021(中国区)
  10. Google Go 初识
  11. BOOST库介绍(二)——BOOST多线程相关库
  12. APMServ5.2.6win10系统Apache、MySQL5.1启动失败解决办法
  13. 关于Big-Endian 和Little-Endian
  14. pycharm2020版本以上中文版教程
  15. C语言的主要用途以及前景开展
  16. 【读书笔记】心理学与生活
  17. 【OpenGrok代码搜索引擎】二、Windows10下基于Linux子系统搭建Opengrok代码搜索引擎
  18. offsetX,offsetLeft,offsetWidth的区别详解
  19. python与工程造价的联系_工程造价和工程预算是一个概念吗?
  20. 云主机装黑果实践(4):阿里轻量机上变色龙bootloader启动问题

热门文章

  1. seafile服务端的搭建
  2. QT 建立信号和槽的联系(事件处理)
  3. [转载] 对称加密与非对称加密
  4. 使用for循环遍历文件
  5. 《领域特定语言》一2.3DSL的问题
  6. Exchange 2010和Exchange 2016共存部署-10:配置多域名证书
  7. Cxf + Spring3.0 入门开发WebService
  8. jquery.form 和MVC4做无刷新上传DEMO
  9. 【Vegas原创】安装rhel6.2,不能进图形化界面的终极解决方法
  10. 打造属于自己的图文符号库