转载

思考 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(不太有用)内正确(有用)类名称进行基于属性的总结:

思考 Swift 中的 MirrorType 协议

可使用新类型 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 现在使用自定义表示形式,而非默认表示形式:

思考 Swift 中的 MirrorType 协议

如果没有 Printable 符合性,println() 和 toString() 也可从实例镜像中获取字符串表示形式。

当前形式的 Swift 反射不仅强大,而且新颖。Swift 针对 WWDC 的新功能确定即将出现,因此,本文的适用期注定较短。同时,如有自省必要,便可知道在哪查看相关信息。

OneAPM Mobile Insight ,监控网络请求及网络错误,提升用户留存。访问OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问OneAPM 官方技术博客。

原文  http://news.oneapm.com/mirrortype-url/
正文到此结束
Loading...