Swift反射API及其用法

猛戳查看最终版@SwiftGG

尽管 Swift 一直在强调强类型、编译时安全和静态调度,但它的标准库仍然提供了反射机制。可能你已经在很多博客文章或者类似TuplesMidi PacketsCore Data 的项目中见过它。也许你刚好对在项目中使用反射机制感兴趣,或者你想更好滴了解反射可以应用的领域,那这篇文章就正是你需要的。文章的内容是基于我在德国法兰克福 Macoun会议上的一次演讲,它对 Swift 的反射 API 做了一个概述。

API 概述

理解这个主题最好的方式就是看API,看它都提供了什么功能。

Mirror

Swift 的反射机制是基于一个叫 Mirrorstruct 来实现的。你为具体的 subject 创建一个 Mirror,然后就可以通过它查询这个对象 subject

在我们创建 Mirror 之前,我们先创建一个可以让我们当做对象来使用的简单数据结构。

import Foundation.NSURL // [译者注]此处应该为import Foundation

public class Store {
    let storesToDisk: Bool = true
}
public class BookmarkStore: Store {
    let itemCount: Int = 10
}
public struct Bookmark {
   enum Group {
      case Tech
      case News
   }
   private let store = {
       return BookmarkStore()
   }()
   let title: String?
   let url: NSURL
   let keywords: [String]
   let group: Group
}

let aBookmark = Bookmark(title: "Appventure", url: NSURL(string: "appventure.me")!, keywords: ["Swift", "iOS", "OSX"], group: .Tech)

创建一个 Mirror

创建 Mirror 最简单的方式就是使用 reflecting 构造器:

public init(reflecting subject: Any)

然后在 aBookmark struct 上使用它:

let aMirror = Mirror(reflecting: aBookmark)
print(aMirror)
// 输出 : Mirror for Bookmark

这段代码创建了 Bookmark 的 Mirror。正如你所见,对象的类型是 Any。这是 Swift 中最通用的类型。Swift 中的任何东西至少都是 Any 类型的1。这样一来 mirror 就可以兼容 struct, class, enum, Tuple, Array, Dictionary, set 等。

Mirror 结构体还有另外三个构造器,但是这三个都是在你需要自定义 mirror 这种情况下使用的。我们会在接下来讨论自定义 mirror 时详细讲解这些额外的构造器。

Mirror 中都有什么?

Mirror struct 中包含几个 types 来帮助确定你想查询的信息。

第一个是 DisplayStyle enum,它会告诉你对象的类型:

public enum DisplayStyle {
    case Struct
    case Class
    case Enum
    case Tuple
    case Optional
    case Collection
    case Dictionary
    case Set
}

这些都是反射 API 的辅助类型。之前我们知道,反射只要求对象是 Any 类型,而且Swift 标准库中还有很多类型为 Any 的东西没有被列举在上面的 DisplayStyle enum 中。如果试图反射它们中间的某一个又会发生什么呢?比如 closure

let closure = { (a: Int) -> Int in return a * 2 }
let aMirror = Mirror(reflecting: closure)

这里你会得到一个 mirror,但是 DisplayStylenil 2

也有提供给 Mirror 的子节点使用的 typealias

public typealias Child = (label: String?, value: Any)

所以每个 Child 都包含一个可选的 labelAny 类型的 value。为什么 labelOptional 的?如果你仔细考虑下,其实这是非常有意义的,并不是所有支持反射的数据结构都包含有名字的子节点。 struct 会以属性的名字做为 label,但是 Collection 只有下标,没有名字。Tuple 同样也可能没有给它们的条目指定名字。

接下来是 AncestorRepresentation enum 3:

public enum AncestorRepresentation {
    /// 为所有 ancestor class 生成默认 mirror。
    case Generated
    /// 使用最近的 ancestor 的 customMirror() 实现来给它创建一个 mirror。
    case Customized(() -> Mirror)
    /// 禁用所有 ancestor class 的行为。Mirror 的 superclassMirror() 返回值为 nil。
    case Suppressed
}

这个 enum 用来定义被反射的对象的父类应该如何被反射。也就是说,这只应用于 class 类型的对象。默认情况(正如你所见)下 Swift 会为每个父类生成额外的 mirror。然而,如果你需要做更复杂的操作,你可以使用 AncestorRepresentation enum 来定义父类被反射的细节。我们会在下面的内容中进一步研究这个。

如何使用一个 Mirror

现在我们有了给 Bookmark 类型的对象aBookmark 做反射的实例变量 aMirror。可以用它来做什么呢?

下面列举了 Mirror 可用的属性 / 方法:

  • let children: Children:对象的子节点。
  • displayStyle: Mirror.DisplayStyle?:对象的展示风格
  • let subjectType: Any.Type:对象的类型
  • func superclassMirror() -> Mirror?:对象父类的 mirror

下面我们会分别对它们进行解析。

displayStyle

很简单,它会返回 DisplayStyle enum 的其中一种情况。如果你想要对某种不支持的类型进行反射,你会得到一个空的 Optional 值(这个之前解释过)。

print (aMirror.displayStyle)
// 输出: Optional(Swift.Mirror.DisplayStyle.Struct)
// [译者注]此处输出:Optional(Struct)

children

这会返回一个包含了对象所有的子节点的 AnyForwardCollection<Child>。这些子节点不单单限于 Array 或者 Dictionary 中的条目。诸如 struct 或者 class 中所有的属性也是由 AnyForwardCollection<Child> 这个属性返回的子节点。AnyForwardCollection 协议意味着这是一个支持遍历的 Collection 类型。

for case let (label?, value) in aMirror.children {
    print (label, value)
}
//输出:
//: store main.BookmarkStore
//: title Optional("Appventure")
//: url appventure.me
//: keywords ["Swift", "iOS", "OSX"]
//: group Tech

SubjectType

这是对象的类型:

print(aMirror.subjectType)
//输出 : Bookmark
print(Mirror(reflecting: 5).subjectType)
//输出 : Int
print(Mirror(reflecting: "test").subjectType)
//输出 : String
print(Mirror(reflecting: NSNull()).subjectType)
//输出 : NSNull

然而,Swift 的文档中有下面一句话:

“当 self 是另外一个 mirrorsuperclassMirror() 时,这个类型和对象的动态类型可能会不一样“

SuperclassMirror

这是我们对象父类的 mirror。如果这个对象不是一个类,它会是一个空的 Optional 值。如果对象的类型是基于类的,你会得到一个新的 Mirror

// 试试 struct
print(Mirror(reflecting: aBookmark).superclassMirror())
// 输出: nil
// 试试 class
print(Mirror(reflecting: aBookmark.store).superclassMirror())
// 输出: Optional(Mirror for Store)

实例

StructCore Data

假设我们在一个叫 Books Bunny 的新兴高科技公司工作,我们以浏览器插件的方式提供了一个人工智能,它可以自动分析用户访问的所有网站,然后把相关页面自动保存到书签中。

现在是 2016 年,Swift 已经开源,所以我们的后台服务端肯定是用 Swift 编写。因为在我们的系统中同时有数以百万计的网站访问活动,我们想用 struct 来存储用户访问网站的分析数据。不过,如果我们 AI 认定某个页面的数据是需要保存到书签中的话,我们需要使用 CoreData 来把这个类型的对象保存到数据库中。

现在我们不想为每个新建的 struct 单独写自定义的 Core Data 序列化代码。而是想以一种更优雅的方式来开发,从而可以让将来的所有 struct 都可以利用这种方式来做序列化。

那么我们该怎么做呢?

协议

记住,我们有一个 struct,它需要自动转换为 NSManagedObjectCore Data)。

如果我们想要支持不同的 struct 甚至类型,我们可以用协议来实现,然后确保我们需要的类型符合这个协议。所以我们假想的协议应该有哪些功能呢?

  • 第一,协议应该允许自定义我们想要创建的Core Data 实体的名字
  • 第二,协议需要提供一种方式来告诉它如何转换为 NSManagedObject

我们的 protocol 看起来是下面这个样子的:

protocol StructDecoder {
    // 我们 Core Data 实体的名字
    static var EntityName: String { get }
    // 返回包含我们属性集的 NSManagedObject
    func toCoreData(context: NSManagedObjectContext) throws -> NSManagedObject //[译者注]使用 NSManagedObjectContext 需要 import CoreData
}

toCoreData 方法使用了 Swift 2.0 新的异常处理来抛出错误,如果转换失败,会有几种错误情况,这些情况都在下面的 ErrorType enum 进行了列举:

enum SerializationError: ErrorType {
    // 我们只支持 struct
    case StructRequired
    // 实体在 Core Data 模型中不存在
    case UnknownEntity(name: String)
    // 给定的类型不能保存在 core data 中
    case UnsupportedSubType(label: String?)
}

上面列举了三种转换时需要注意的错误情况。第一种情况是我们试图把它应用到非 struct 的对象上。第二种情况是我们想要创建的 entity 在 Core Data 模型中不存在。第三种情况是我们想要把一些不能存储在 Core Data 中的东西保存到 Core Data 中(即 enum)。

让我们创建一个 struct 然后为其增加协议一致性:

Bookmark struct

struct Bookmark {
   let title: String
   let url: NSURL
   let pagerank: Int
   let created: NSDate
}

下一步,我们要实现 toCoreData 方法。

协议扩展

当然我们可以为每个 struct 都写新的 toCoreData 方法,但是工作量很大,因为 struct 不支持继承,所以我们不能使用基类的方式。不过我们可以使用 protocol extension 来扩展这个方法到所有相符合的 struct

extension StructDecoder {
    func toCoreData(context: NSManagedObjectContext) throws -> NSManagedObject {
    }
}

因为扩展已经被应用到相符合的 struct,这个方法就可以在 struct 的上下文中被调用。因此,在协议中,self 指的是我们想分析的 struct

所以,我们需要做的第一步就是创建一个可以写入我们 Bookmark struct 值的NSManagedObject。我们该怎么做呢?

一点 Core Data

Core Data 有点啰嗦,所以如果需要创建一个对象,我们需要如下的步骤:

  1. 获得我们需要创建的实体的名字(字符串)
  2. 获取 NSManagedObjectContext,然后为我们的实体创建 NSEntityDescription
  3. 利用这些信息创建 NSManagedObject

实现代码如下:

// 获取 Core Data 实体的名字
let entityName = self.dynamicType.EntityName

// 创建实体描述
// 实体可能不存在, 所以我们使用 ‘guard let‘ 来判断,如果实体
// 在我们的 core data 模型中不存在的话,我们就抛出错误
guard let desc = NSEntityDescription.entityForName(entityName, inManagedObjectContext: context)
    else { throw UnknownEntity(name: entityName) } // [译者注] UnknownEntity 为 SerializationError.UnknownEntity

// 创建 NSManagedObject
let managedObject = NSManagedObject(entity: desc, insertIntoManagedObjectContext: context)

实现反射

下一步,我们想使用反射 API 来读取 bookmark 对象的属性然后把它写入到 NSManagedObject 实例中。

// 创建 Mirror
let mirror = Mirror(reflecting: self)

// 确保我们是在分析一个 struct
guard mirror.displayStyle == .Struct else { throw SerializationError.StructRequired }

我们通过测试 displayStyle 属性的方式来确保这是一个 struct

所以现在我们有了一个可以让我们读取属性的 Mirror,也有了一个可以用来设置属性的 NSManagedObject。因为 mirror 提供了读取所有 children 的方式,所以我们可以遍历它们并保存它们的值。方式如下:

for case let (label?, value) in mirror.children {
    managedObject.setValue(value, forKey: label)
}

太棒了!但是,如果我们试图编译它,它会失败。原因是 setValueForKey 需要一个 AnyObject? 类型的对象,而我们的 children 属性只返回一个 (String?, Any) 类型的 tuple——也就是说 valueAny 类型,但是我们需要 AnyObject 类型的。为了解决这个问题,我们要测试 valueAnyObject 协议一致性。这也意味着如果得到的属性的类型不符合 AnyObject 协议(比如 enum),我们就可以抛出一个错误。

let mirror = Mirror(reflecting: self)

guard mirror.displayStyle == .Struct
  else { throw SerializationError.StructRequired }

for case let (label?, anyValue) in mirror.children {
    if let value = anyValue as? AnyObject {
    managedObject.setValue(child, forKey: label) // [译者注] 正确代码为:managedObject.setValue(value, forKey: label)
    } else {
    throw SerializationError.UnsupportedSubType(label: label)
    }
}

现在,只有在 childAnyObject 类型的时候我们才会调用 setValueForKey 方法。

然后唯一剩下的事情就是返回 NSManagedObject。完整的代码如下:

extension StructDecoder {
    func toCoreData(context: NSManagedObjectContext) throws -> NSManagedObject {
    let entityName = self.dynamicType.EntityName

    // 创建实体描述
    guard let desc = NSEntityDescription.entityForName(entityName, inManagedObjectContext: context)
        else { throw UnknownEntity(name: entityName) } // [译者注] UnknownEntity 为 SerializationError.UnknownEntity

    // 创建 NSManagedObject
    let managedObject = NSManagedObject(entity: desc, insertIntoManagedObjectContext: context)

    // 创建一个 Mirror
    let mirror = Mirror(reflecting: self)

    // 确保我们是在分析一个 struct
    guard mirror.displayStyle == .Struct else { throw SerializationError.StructRequired }

    for case let (label?, anyValue) in mirror.children {
        if let value = anyValue as? AnyObject {
        managedObject.setValue(child, forKey: label) // [译者注] 正确代码为:managedObject.setValue(value, forKey: label)
        } else {
        throw SerializationError.UnsupportedSubType(label: label)
        }
    }

    return managedObject
    }
}

搞定,我们现在已经把 struct 转换为 NSManagedObject 了。

性能

那么,速度如何呢?这个方法可以在生产中应用么?我做了一些测试:

创建 2000 个 NSManagedObject
原生: 0.062 seconds
反射: 0.207 seconds

这里的原生是指创建一个 NSManagedObject,然后通过 setValueForKey 设置属性值。如果你在 Core Data 内创建一个 NSManagedObject 子类然后把值直接设置到属性上(没有了动态 setValueForKey 的开销),速度可能更快。

所以正如你所见,使用反射使创建 NSManagedObject 的性能下降了3.5倍。当你在数量有限的项目上使用这个方法,或者你不关心处理速度时,这是没问题的。但是当你需要反射大量的 struct 时,这个方法可能会大大降低你 app 的性能。

自定义 Mirror

我们之前已经讨论过,创建 Mirror 还有其他的选项。这些选项是非常有用的,比如,你想自己定义 mirror对象的哪些部分是可访问的。对于这种情况 Mirror Struct 提供了其他的构造器。

Collection

第一个特殊 init 是为 Collection 量身定做的:

public init<T, C : CollectionType where C.Generator.Element == Child>
  (_ subject: T, children: C,
   displayStyle: Mirror.DisplayStyle? = default,
   ancestorRepresentation: Mirror.AncestorRepresentation = default)

与之前的 init(reflecting:) 相比,这个构造器允许我们定义更多反射处理的细节。

  • 它只对 Collection 有效
  • 我们可以设定被反射的对象以及对象的 childrenCollection 的内容)

class 或者 struct

第二个可以在 class 或者 struct 上使用。

public init<T>(_ subject: T,
  children: DictionaryLiteral<String, Any>,
  displayStyle: Mirror.DisplayStyle? = default,
  ancestorRepresentation: Mirror.AncestorRepresentation = default)

有意思的是,这里是由你指定对象的 children (即属性),指定的方式是通过一个 DictionaryLiteral,它有点像字典,可以直接用作函数参数。如果我们为 Bookmark struct 实现这个构造器,它看起来是这样的:

extension Bookmark: CustomReflectable {
    func customMirror() -> Mirror { // [译者注] 此处应该为 public func customMirror() -> Mirror {
    let children = DictionaryLiteral<String, Any>(dictionaryLiteral:
    ("title", self.title), ("pagerank", self.pagerank),
    ("url", self.url), ("created", self.created),
    ("keywords", self.keywords), ("group", self.group))

    return Mirror.init(Bookmark.self, children: children,
        displayStyle: Mirror.DisplayStyle.Struct,
        ancestorRepresentation:.Suppressed)
    }
}

如果现在我们做另外一个性能测试,会发现性能甚至略微有所提升:

创建 2000 个 NSManagedObject
原生: 0.062 seconds
反射: 0.207 seconds
反射: 0.203 seconds

但这个工作几乎没有任何价值,因为它与我们之前反射 struct 成员变量的初衷是相违背的。

用例

所以留下来让我们思考的问题是什么呢?好的反射用例又是什么呢?很显然,如果你在很多 NSManagedObject 上使用反射,它会大大降低你代码的性能。同时如果只有一个或者两个 struct,根据自己掌握的struct 领域的知识编写一个序列化的方法会更容易,更高性能且更不容易让人困惑。

而本文展示反射技巧可以当你在有很多复杂的 struct ,且偶尔想对它们中的一部分进行存储时使用。

例子如下:

  • 设置收藏夹
  • 收藏书签
  • 加星
  • 记住上一次选择
  • 在重新启动时存储AST打开的项目
  • 在特殊处理时做临时存储

除了这些,反射当然还有其他的使用场景:

  • 遍历 tuple
  • 对类做分析
  • 运行时分析对象的一致性
  • 自动生成详细日志 / 调试信息(即外部生成对象)

讨论

反射 API 主要做为 Playground 的一个工具。符合反射 API 的对象可以很轻松滴就在 Playground 的侧边栏中以分层的方式展示出来。尽管它的性能不是最优的,在 Playground 之外仍然有很多有趣的应用场景,这些应用场景我们在用例章节中都讲解过。

更多信息

反射 API 的源文件注释非常详细,我强烈建议每个人都去看看。

同时,GitHub 上的 CoreValue 项目展示了关于这个技术更详尽的实现,它可以让你很轻松滴把 struct 编码成 CoreData,或者把 CoreData 解码成 struct

1、实际上,Any 是一个空的协议,所有的东西都隐式滴符合这个协议。2、更确切地说,是一个空的可选类型。3、我对注释稍微做了简化。

附:

文章可执行代码工程地址

时间: 2024-11-10 08:06:10

Swift反射API及其用法的相关文章

反射API

class HelloWorld{ public function sayHelloTo($name,$value){ return 'Hello,'.$name.$value; }}$reflectionMethod=new ReflectionMethod('HelloWorld', 'sayHelloTo');echo $reflectionMethod->invokeArgs(new HelloWorld(), array('kcc','fuck','back')); 反射API,布布扣

PHP反射API (转)

http://www.cnblogs.com/zyf-zhaoyafei/p/4922893.html 近期忙着写项目,没有学习什么特别新的东西,所以好长时间没有更新博客.我们的项目用的是lumen,是基于laravel的一个轻量级框架,我看到里面用到了一些反射API机制来帮助动态加载需要的类.判断方法等,所以本篇文章就把在PHP中经常用到的反射API给大家分享一下吧,想学习反射API的同学可以看看. 说起反射ApI,自我感觉PHP中的反射ApI和java中的java.lang.reflect包

vB SendMessage API 简单用法

vB SendMessage API 简单用法 1. 在Windows编程中,向文本框控件.列表控件.按钮控件等是我们最常接触的控件了.但是在VB中这些控件有时无法实现我们的需要.在这时,我们只要简单的利用Windows API函数就可以扩充这些控件的功能了.顾名思义,SendMessage函数就是向窗口(这里的窗口指的是向按钮.列表框.编辑框等具有hWnd属性的控件)发送消息的函数,该函数的定义如下:Declare Function SendMessage Lib "user32"

反射——反射API,使用反射创建数组

反射API Java.lang.Reflect库 ①   Class类与Java.lang.Reflect类库一起对反射的概念进行支持. ②   java.lang包下: a)         Class<T>:表示对一个正在运行的Java应用程序中的类和接口,是Reflection的起源. ③   java.lang.reflect包下: a)         Field类:代表类的成员变量(成员变量也称类的属性). b)         Method类:代表类的方法. c)        

Atitit.跨语言反射api&#160;兼容性提升与增强&#160;java&#160;c#。Net&#160;&#160;php&#160;&#160;js

Atitit.跨语言反射api 兼容性提升与增强 java c#.Net  php  js 1. 什么是反射1 1.1.       反射提供的主要功能:2 1.2.       实现反射的过程:2 2. 类反射中所必须的类: 的类反射所需要的类并不多,它们分别是:Field.Constructor.Method.Class.Object,2 3. . 反射的用处3 3.1. 可视化 3 3.2. 系统的灵活性.可扩展性 3 3.3. Json xml序列化3 3.4. 函数指针3 3.5. R

VC中常见API函数用法(经验版)

1.设置对话框为无边框方法 ModifyStyle(WS_CAPTION | WS_THICKFRAME, 0, SWP_FRAMECHANGED); 2.设置控件灰色与不灰色 void CMthread1Dlg::OnStop() { // TODO: Add your control notification handler code here m_bRun = FALSE; GetDlgItem(IDC_START)->EnableWindow(TRUE); GetDlgItem(IDC_

Java反射机制的用法

Java的反射机制允许你在程序运行的过程中获取类定义的细节.有时候在程序运行的时候才得知要调用哪个方法,这时候反射机制就派上用场了. 获取类 类的获取方法有以下几种: forName().通过Class.forName()获取与字符串向对应的类.比如\lstinline{Class.forName("java.lang.String");}. getClass().每个对象都有这个方法.比如\lstinline{Foo foo; ...; foo.getClass();}. 类名.cl

JDK1.7新特性(4):java语言动态性之反射API

直接通过一个代码示例来熟悉java中通过反射来对构造函数/域以及方法处理的相关API: 1 package com.rampage.jdk7.chapter2; 2 3 import java.lang.reflect.Array; 4 import java.lang.reflect.Constructor; 5 import java.lang.reflect.Field; 6 import java.lang.reflect.InvocationTargetException; 7 imp

利用反射api查找一个类的详细信息

说到这个实例,首先介绍下本人,我是一个php程序员,从事drupal开发2年多,可以说从实习开始就接触这个,至今没有换过,drupal给我的感觉是俩字"强大",今天写一个views的字段,然后需要继承views的views_handler_field类,还要自己实现里面的一些方法,走一些自己的配置设置,查看这个类的时候,发现实在是太多信息了,并且做了好些继承,于是我就想要是能实现一个功能,传入一个类名,然后就能返回类的所有信息(包括,属性,方法,继承,接口,并且这些类所放置的文件位置,