原文地址 https://swift.org/blog/how-mirror-works/?utm_source=mybridge&utm_medium=blog&utm_campaign=read_more
虽然 swift 是一门特别强调静态类型的语言,但是它同时也支持丰富的元数据类型,也就是说允许在运行时检查代码和操作任意值。这种特性通过 Mirror
API 暴露给开发人员。你可能会很好奇,Mirror
是如何在如此强调静态类型的语言中工作的?让我们来一探究竟。
免责声明
接下来所讲得内容是内部实现的细节,所涉及的代码是在当前版本中的写法,在将来可能会发生改变。当 ABI 稳定后,元数据将变成固定和可信赖的格式,但是现在它仍会发生改变。如果你写常规的 swift 代码,不要依赖于元数据。如果你要编写的代码所要实现的是比 Mirror
API 所提供的更复杂的反射机制的话,本篇文章将会启发你它们是如何一起配合的,但是要记住所有一切在未来都可能发生改变。
接口
Mirror(reflecting:)
初始化器得参数接受任意值。返回的 Mirror
实例提供关于这个值的信息,主要是它所包含的子项。每个子项包含一个值和一个可选的标签。然后,你就可以在不需要知道编译时的任何类型信息的情况下,使用 Mirror
去遍历一个对象的所有子项。
Mirror
允许类型在遵守 CustomReflectable
协议的条件下,提供自定义的表现。这种特性对于那些希望展示比从内省中获得更多信息的类来说是特别有用的。例如,Array
遵守 CustomReflectable
协议并且将所有的元素暴露为未标签化的子项。 Dictionary
使用这种特性将所有的键值对暴露为标签化的子项。
对于所有其他的类型而言, Mirror
在值的真实内容基础上做了一些魔法操作生成一列子项。对于结构体和类而言,它将存储的属性作为子项展示。对于元组而言,它展示的是元组的元素。枚举则是展示每种情况和其对应的值(如果有的话)。
黑魔法是如何工作的?让我们来看一看。
最近碰到一个需求,类似于下图所示,几个 UIButton
排列在一起,在某个事件触发时,隐藏或者显示中间的 button
,重点是隐藏中间的 button
时,上下 button
之间的间距保持为原来的间距。比较好的做法是重写 alignmentRectInsets
的 get 方法,当然更新约束的方法也是 OK 的。直接创建子类去重写 alignmentRectInsets
的 get 方法不够优雅,因而想到创建分类, swizzle UIView
的 alignmentRectInsets
的 get 方法,在自己的自定义方法中去完成真正的修改逻辑。
原以为写完分类,在项目中调用再 run 一下就大功告成了,然而,我还是 naive 啊。运行项目后,发现竟然没有 work ,打断点调试,发现 swizzle method 没有调用。没有调用的原因在于 UIButton
类本身的实现已经重写了 alignmentRectInsets
的 get 方法,所以,不会再调用 UIView
的相同方法了。那怎么办呢?最简单的办法就是给 UIButton
创建分类而不是 UIView
,但是感觉这样写不是很好,有些冗余。
经过一番研究后,想到可以利用 KVO 的机制来解决这个问题。
前言
自 iOS 9 以后,Apple 加入了悬浮窗调试工具,也就是 UIDebuggingInformationOverlay
。利用它我们可以做到很多事情,例如:查看视图层级,控制器层级,页面中的变量,测量等等。那么我们如何开启这个调试工具呢?只需要添加如下的代码:
|
|
这段代码的实际意义可以转换成以下两行代码:
|
|
调用成功后,我们就能看到悬浮窗的庐山真面目了。