思考 Swift 中的 MirrorType 协议
Swift中的反射非常有限,仅允许以只读方式访问元数据的类型子集。或许 Swift 因有严格的类型检验而不需要反射。编译时已知各种类型,便不再需要进行进一步检查或区分。然后大量的 Cocoa API 会立即给实例分配“AnyObject”类型,用户只能想方设法去做类型匹配。
而这里将回顾 Swift 中的反射、镜像类型以及将它们结合起来的 MirrorType 协议。
MirrorType
反射的切入点为「reflect」函数,「reflect」函数可将任何类型作为其单参数并返回一个 MirrorType。对于 Swift 标准库而言,MirrorType “格格不入”:协议作为类型。除了普遍存在的 AnyObject,迄今为止再无以此方式使用的其他协议。独特的 MirrorType,“其返回取决于你给‘reflect’传递的类型”,reflect是Swift 内部的构件,为Array、Dictionary、Optional、Range等类型,以及tructs、classes、tuples、metatypes等通用类型定义镜像。
MirrorType 为Swift 提供了初期的反射 API,封装值及其类型、子类信息(和实例的不同表示形式)。镜像具有下列属性:
- value:访问初始反射值(任意类型)。
- valueType:初始反射值的 类型——相当于 value.dynamicType。
- count:逻辑子类的数量。对 Array 或 Set 等集合而言,count 为元素的数量;对结构而言,count 为存储属性的数量。
- disposition:MirrorDisposition 列举的值,旨在辅助 IDE 选择显示值的方式。MirrorDisposition 有11个实例:
- IndexContainer、KeyContainer、MembershipContainer、Container用于集合。
- Optional:用于可选值。reflect() 会略过隐式打开的可选值,直接获取打开值的反射。
- Aggregate:用于将Swift类型过渡至 Objective-C,以及对Objective-C 类型扩展的 Objective-C 类型。例如,Float 有一个“Aggregate”版本,其中non-bridged Float80 会返回Struct和UIView(“Reflectable”扩展 ),它有一个Aggregate版本,而原始 UIBarButtonItem 返回 ObjCObject。
- ObjCObject:与- Aggregate相比,ObjCObject 用于未扩展的 Objective-C 类。
- Tuple:用于元组值。
- Struct、Class、Enum:以上所有类型的候补类型。
- objectIdentifier:类或次型实例的唯一对象标识符。
- summary:值的字符串表示形式。
* quickLookObject:具有值的视觉或文本表示的 QuickLookObject 实例。其性能与debugQuickLookObject的类似。几周前已对此进行了说明。
此外,镜像有基于 Int 的脚注,脚注为各子类返回一个(String, MirrorType)元组,即属性/密钥/索引的名称以及值的镜像。
可是如何使用 MirrorType 呢?假设元组中有一组用于彩票的数,首先需将这些数转换为 [Int] 数组:
let lotteryTuple = (4, 8, 15, 16, 23, 42)
可采用 reflect() 对数组元素进行迭代,而非一个个地(如 lotteryType.0、lotteryTuple.1 等)抽取元组中的数。
// create a mirror of the tuple
let lotteryMirror = reflect(lotteryTuple)
// loop over the elements of the mirror to build an array
var lotteryArray: [Int] = []
for i in 0..<lotteryMirror.count {
    let (index, mirror) = lotteryMirror[i]
    if let number = mirror.value as? Int {
        lotteryArray.append(number)
    }
}
println(lotteryArray)   // [4, 8, 15, 16, 23, 42]
不错。
Mapping a Mirror
如果可以在镜像中映射元素,则映射实例的属性或元素可能更加容易。现在编写具有任何类型实例和转化闭包的 mapReflection 函数:
func mapReflection<T, U>(x: T, @noescape transform: (String, MirrorType) -> U) -> [U] {
    var result: [U] = []
    let mirror = reflect(x)
    for i in 0..<mirror.count {
        result.append(transform(mirror[i]))
    }
    return result
}
现在可非常容易地输出任何实例的逻辑子类:
let printChild: (String, MirrorType) -> () = {
    println("\($0): \($1.value)")
}
mapReflection(lotteryTuple, printChild)
// .0: 4
// .1: 8
// ...
mapReflection(lotteryArray, printChild)
// [0]: 4
// [1]: 8
// ...
mapReflection(CGRect.zeroRect, printChild)
// origin: (0.0, 0.0)
// size: (0.0, 0.0)
使用过 Swift dump 函数的人可能对以上输出比较熟悉。Dump 采用反射递归地印出实例的子类等:
dump(CGRect.zeroRect)
// ▿ (0.0, 0.0, 0.0, 0.0)
//   ▿ origin: (0.0, 0.0)
//     - x: 0.0
//     - y: 0.0
//   ▿ size: (0.0, 0.0)
//     - width: 0.0
//     - height: 0.0
Custom-Cut Mirrors
除 dump 外,Xcode 也广泛使用镜像在 Playground 内显示值,在 Playground 窗口右侧的结果窗格中进行显示或显示捕捉到的值。自定义类型不以自定义镜像开始,因此,自定义类型的显示都待提高。看一看 Playground 内自定义类型的默认性能便能明白自定义的 MirrorType 将如何提升显示性能。
对于自定义类型,可使用简单的结构来保留 WWDCsession 的相关信息:
/// Information for a single WWDC session.
struct WWDCSession {
    /// An enumeration of the different WWDC tracks.
    enum Track : String {
        case Featured         = "Featured"
        case AppFrameworks    = "App Frameworks"
        case Distribution     = "Distribution"
        case DeveloperTools   = "Developer Tools"
        case Media            = "Media"
        case GraphicsAndGames = "Graphics & Games"
        case SystemFrameworks = "System Frameworks"
        case Design           = "Design"
    }
	let number: Int
    let title: String
    let track: Track
    let summary: String?
}
let session801 = WWDCSession(number: 801,
    title: "Designing for Future Hardware",
    track: .Design,
    summary: "Design for tomorrow's products today. See examples...")
默认情况下,WWDCSession 实例的反射采用嵌入式 _StructMirror 类型。这样可确保仅对捕捉到的value pane(不太有用)内正确(有用)类名称进行基于属性的总结:

可使用新类型 WWDCSessionMirror 获得内容更加丰富的 WWDCSession 表示形式。此类型须符合 MirrorType,包括上述所有属性:
struct WWDCSessionMirror: MirrorType {
    private let _value: WWDCSession
    init(_ value: WWDCSession) {
        _value = value
    }
    var value: Any { return _value }
    var valueType: Any.Type { return WWDCSession.self }
    var objectIdentifier: ObjectIdentifier? { return nil }
    var disposition: MirrorDisposition { return .Struct }
    // MARK: Child properties
    var count: Int { return 4 }
    subscript(index: Int) -> (String, MirrorType) {
        switch index {
        case 0:
            return ("number", reflect(_value.number))
        case 1:
            return ("title", reflect(_value.title))
        case 2:
            return ("track", reflect(_value.track))
        case 3:
            return ("summary", reflect(_value.summary))
        default:
            fatalError("Index out of range")
        }
    }
    // MARK: Custom representation
    var summary: String {
        return "WWDCSession \(_value.number) [\(_value.track.rawValue)]: \(_value.title)"
    }
    var quickLookObject: QuickLookObject? {
        return .Text(summary)
    }
}
summary 和 quickLookObject 属性中将提供 WWDCSession 的自定义表示形式——适当格式化的字符串。尤其应注意完全手动使用 count 和脚注。由于默认镜像类型会忽略私有及内部访问修饰符,因此,自定义镜像可用于隐藏使用细节,包括来自反射的细节。
最后,我们必须通过增加与 Reflectable 协议的一致性将 WWDCSession 链接到其自定义镜像。符合性仅需一个新方法,即返回 MirrorType 的 getMirror()——在此情况下,新的 WWDCSessionMirror 如下所示:
extension WWDCSession : Reflectable {
    func getMirror() -> MirrorType {
        return WWDCSessionMirror(self)
    }
}
就是这样!Playground 现在使用自定义表示形式,而非默认表示形式:

如果没有 Printable 符合性,println() 和 toString() 也可从实例镜像中获取字符串表示形式。
当前形式的 Swift 反射不仅强大,而且新颖。Swift 针对 WWDC 的新功能确定即将出现,因此,本文的适用期注定较短。同时,如有自省必要,便可知道在哪查看相关信息。
OneAPM Mobile Insight,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客
思考 Swift 中的 MirrorType 协议的更多相关文章
- Swift中的可选协议和方法的历史渊源
		@objc protocol Transaction { func commit() -> Bool optional func isComplete() -> Bool } 以上协议被标 ... 
- Swift Explore - 关于 Swift 中的 isEqual 的一点探索
		在我们进行 App 开发的时候,经常会用到的一个操作就是判断两个对象是否相等.比如两个字符串是否相等.而所谓的 相等 有着两层含义.一个是值相等,还有一个是引用相等.如果熟悉 Objective-C ... 
- Swift基础--通知,代理和block的使用抉择以及Swift中的代理
		什么时候用通知,什么时候用代理,什么时候用block 通知 : 两者关系层次太深,八竿子打不着的那种最适合用通知.因为层级结构深了,用代理要一层一层往下传递,代码结构就复杂了 代理 : 父子关系,监听 ... 
- Swift中声明协议中的class关键字的作用
		大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 最近在Cocos2D编程for Swift中看到以下一个代码片 ... 
- Swift 中的协议
		Swift 中的协议协议是为方法.属性等定义一套规范,没有具体的实现,类似于Java中的抽象接口,它只是描述了方法或属性的骨架,而不是实现.方法和属性实现还需要通过定义类,函数和枚举完成. 协议定义 ... 
- swift中的结构体和枚举
		Swift 里的结构体非常特殊. 类是面向对象编程语言中传统的结构单元.和结构体相比,Swift 的类支持实现继承,(受限的)反射,析构函数和多所有者. 既然类比结构体强大这么多,为什么还要使用结构体 ... 
- 在Swift中应用Grand Central Dispatch(上)转载自的goldenfiredo001的博客
		尽管Grand Central Dispatch(GCD)已经存在一段时间了,但并非每个人都知道怎么使用它.这是情有可原的,因为并发很棘手,而且GCD本身基于C的API在 Swift世界中很刺眼. 在 ... 
- Swift中的HTTP请求
		iOS开发中大部分App的网络数据交换是基于HTTP协议的.本文将简单介绍在Swift中使用HTTP进行网络请求的几种方法. 注意:网络请求完成后会获得一个NSData类型的返回数据,如果数据格式为J ... 
- swift开发之--Protocol(协议)
		使用object-c语言的同学们肯定对协议都不陌生,但在swift中苹果将protocol这种语法发扬的更加深入和彻底. Swift中的protocol不仅能定义方法还能定义属性,配合extensio ... 
随机推荐
- asp.net下载的方法
			public void DownLoad( ){ string filePath = Server.MapPath( @"\UserFile\" );//这里注意了,你得指明要下载 ... 
- 【排障】使用DiskGenius修复0扇区损坏
			用PE引导启动进入PE后打开DiskGenius软件 "硬盘"图形菜单------选择驱动器符号(例如C) 主界面中显示该硬盘的分区格式为FAT32,起始柱面0,起始磁头65. 在 ... 
- samba服务器与远程登录ssh
			作者:相思羽 出处:http://www.cnblogs.com/xiang-siyu 欢迎转载,也请保留这段声明.谢谢! deepin安装与配置samba服务器 安装 apt-get insta ... 
- php文件上传之单文件上传
			为了简单一些,php文件跟form表单写在了一个文件里. php单文件上传----> <!DOCTYPE html> <html> <head> <me ... 
- 20151215jqueryUI--dialog代码备份
			$(function () { $('#search_button').button(); /*$('#reg_a').click(function() { $('#reg').dialog(); } ... 
- Sql2008的行列转换之行转列
			今天在工作的时候遇到了行列转换的问题,记得去年有一段时间经常写,但是许久不用已经记不太得了.好记性不如烂笔头,忙完之后赶紧记录一下. 关键字:PIVOT(行转列),UNPIVOT(列转行) 先说说 P ... 
- js和css分别实现透明度的动画实现
			js实现 两个函数 即setInterval和setTimeout setTimeout((function(){})(1/10),1*100) 该函数有两个参数,第一个为执行的函数,第二个为事件参数 ... 
- 关于Spring AOP和IOC的一些总结
			Spring官方网站:https://spring.io/ 最早对象的创建是有new关键字,但是如果产生的类比较繁多或者复杂,就用工厂代替new关键字,但是工厂的控制能力有限,譬如对产生对象的生命周期 ... 
- 九度OJ 1079 手机键盘
			题目地址:http://ac.jobdu.com/problem.php?pid=1079 题目描述: 按照手机键盘输入字母的方式,计算所花费的时间 如:a,b,c都在“1”键上,输入a只需要按一次, ... 
- MySQL主从同步原理 部署【转】
			一.主从的作用:1.可以当做一种备份方式2.用来实现读写分离,缓解一个数据库的压力二.MySQL主从备份原理master 上提供binlog ,slave 通过 I/O线程从 master拿取 bin ... 
