Swift反射API及其用法

Swift反射API及其用法,第1张

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

猛戳查看最终版@SwiftGG

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

API 概述

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

Mirror

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

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

import Foundation.NSURL // [译者注]此处应该为import Foundationpublic 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)//输出 : Bookmarkprint(Mirror(reflecting: 5).subjectType)//输出 : Intprint(Mirror(reflecting: "test").subjectType)//输出 : Stringprint(Mirror(reflecting: NSNull()).subjectType)//输出 : NSNull

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

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

SuperclassMirror

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

// 试试 structprint(Mirror(reflecting: aBookmark).superclassMirror())// 输出: nil// 试试 classprint(Mirror(reflecting: aBookmark.store).superclassMirror())// 输出: Optional(Mirror for Store)
实例 StructCore Data

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

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

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

那么我们该怎么做呢?

协议

记住,我们有一个 struct,它需要自动转换为 NSManagedobject (Core 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 有点啰嗦,所以如果需要创建一个对象,我们需要如下的步骤:

获得我们需要创建的实体的名字(字符串) 获取 NSManagedobjectContext,然后为我们的实体创建 NSEntityDescription 利用这些信息创建 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// 创建 NSManagedobjectlet managedobject = NSManagedobject(entity: desc,insertIntoManagedobjectContext: context)
实现反射

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

// 创建 Mirrorlet mirror = Mirror(reflecting: self)// 确保我们是在分析一个 structguard 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)        } 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>,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、我对注释稍微做了简化。

附:
文章可执行代码工程地址

总结

以上是内存溢出为你收集整理的Swift反射API及其用法全部内容,希望文章能够帮你解决Swift反射API及其用法所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1082549.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存