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

Default WWDCSession Representation

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

Custom WWDCSession Representation

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,077评论 4 62
  • 最近总是和你闹脾气,昨天晚上你对我稍微对我吼了一下导致我哭的很伤心。起初你并没有哄我,基本每次都这样,但我心...
    培你知晓阅读 221评论 2 0
  • (一)模仿创造伟大的革新 天才式的革新从一开始几乎都起步于模仿 (二)模仿促进有效的学习 如此看来,模仿实际上是创...
    青青狐阅读 1,123评论 0 5
  • 我们的生命,就是以不断出发的姿势得到重生。为某些只有自己才能感知的来自内心的召唤,走在路上,无法停息。——安...
    对角夫妇的骑行阅读 199评论 0 1