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 官方博客


OneAPM蓝海讯通
11.4k 声望510 粉丝

Software makes the world run. OneAPM makes the software run.