Swift是否支持反射?

116

Swift是否支持反射?例如,是否存在类似于valueForKeyPath:setValue:forKeyPath:用于Swift对象的功能?

实际上,它是否具有动态类型系统,类似于Objective-C中的obj.class


1
我在Swift中创建了一个反射的帮助类。你可以在这里找到它:https://github.com/evermeer/EVReflection - Edwin Vermeer
2
他们在Swift 2.0中删除了reflect。这是我枚举属性和值的方法[链接](https://dev59.com/LF8e5IYBdhLWcg3wssIV#32969324) - modus
6个回答

86

5
好的,我会尽力为您进行翻译。以下是需要翻译的内容:嗯,我认为这不算是真正的反映。首先,它是只读的。在我看来,这只是一种在Xcode中启用调试的hack方法。协议“Mirror”实际上多次引用了“IDE”一词。 - Sulthan
7
仅适用于属性,不支持方法反射。 - Sulthan
12
这是 gist 的作者,我在 WWDC 的 Swift 实验室里编写了这个,想与大家分享。众所周知,我和工程师们确认了 reflect() 函数存在以支持 Playground。但你仍然可以通过它来玩乐 :) 在这里,我使用它编写了一个小型的模型序列化程序。将其粘贴到 Playground 中并享受乐趣:https://gist.github.com/mchambers/67640d9c3e2bcffbb1e2 - Marc Chambers
1
请查看答案https://dev59.com/JGAg5IYBdhLWcg3wBXAR#25345461,了解如何使用`_stdlib_getTypeName`。 - Klaas
1
这里有一个类,可以反射基类和可选项(不是类型),并支持NSCoding以及从字典解析和转换:https://github.com/evermeer/EVCloudKitDao/blob/master/AppMessage/AppMessage/CloudKit/EVReflection.swift - Edwin Vermeer
显示剩余2条评论

46

如果一个类继承 NSObject,那么所有的 Objective-C 反射和动态性都能够使用。这包括:

  • 请求一个类的方法和属性,并调用方法或设置属性。
  • 交换方法实现。(为所有实例添加功能)
  • 在运行时生成并分配一个新子类。(为给定实例添加功能)

这种功能的一个不足之处是对于 Swift 的可选值类型支持不够完善。例如,Int 属性可以被枚举和修改,但 Int? 属性则不能。可选类型可以使用 reflect/MirrorType 部分地进行枚举,但仍然不能被修改。

如果一个类没有继承 NSObject,那么只有新的、非常有限的(正在进行中?)反射工作(参见 reflect/MirrorType),它增加了询问一个实例关于其类和属性的有限能力,但没有上述所有其他功能。

当不扩展 NSObject 或使用 '@objc' 指令时,Swift 默认使用基于静态分派和虚表的分派方式。这种方式更快,但是在没有虚拟机的情况下不允许运行时方法拦截。这种拦截是 Cocoa 的一个基本部分,需要以下类型的功能:

  • Cocoa 优雅的属性观察器。(属性观察器已经内置到 Swift 语言中)
  • 非侵入式地应用诸如日志记录、事务管理(即面向方面编程)之类的横向关注点。
  • 代理、消息转发等。

因此,推荐在使用 Swift 实现 Cocoa/CocoaTouch 应用程序中的类时:

  • 从 NSObject 继承。Xcode 中的新类对话框指向这个方向。
  • 在需要处理性能问题的情况下,可以使用静态分派 - 例如在紧密循环中调用体积非常小的方法。

摘要:

  • Swift 可以像 C++ 一样表现出快速的静态/虚表分派和有限反射。这使得它适用于较低级别或性能密集型应用程序,但没有 C++ 的复杂性、学习曲线或错误风险。
  • 虽然 Swift 是一种编译语言,但是方法调用的消息传递方式增加了像 Ruby 和 Python 等现代语言中发现的反射和动态性,就像 Objective-C 一样,但没有 Objective-C 的遗留语法。

参考数据:方法调用的执行开销:

  • 静态分派:<1.1ns
  • 虚表分派:~1.1ns
  • 动态分派:~4.9ns

(实际性能取决于硬件,但比率将保持相似)。

此外,dynamic 属性允许我们明确指示 Swift 一个方法应该使用动态分派,并因此支持拦截。

public dynamic func foobar() -> AnyObject {
}

2
即使使用Objective-C技术,对于可选的Swift类型似乎也无法正常工作。除非我漏了什么诀窍,否则建议在答案中注明此限制。 - whitneyland

8

文档介绍了一种动态类型系统,主要涉及TypedynamicType

请参见Metatype Type (in Language Reference)

示例:

var clazz = TestObject.self
var instance: TestObject = clazz()

var type = instance.dynamicType

println("Type: \(type)") //Unfortunately this prints only "Type: Metatype"

假设现在 TestObject 继承自 NSObject
var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()

if let testObject = instance as? TestObject {
    println("yes!") //prints "yes!"
}

目前,尚未实现反射。

编辑:我显然是错了,请参见stevex的答案。已经构建了一些简单的只读属性反射,可能是为了允许IDE检查对象内容。


6

在Swift 5中没有reflect关键字,现在您可以使用

struct Person {
    var name="name"
    var age = 15
}

var me = Person()
var mirror = Mirror(reflecting: me)

for case let (label?, value) in mirror.children {
    print (label, value)
}


为什么这个没有被点赞?这非常有用。我将应用它来进行 json 反序列化。 - WestCoastProjects

5

您可能会考虑使用toString()代替。它是公共的并且与_stdlib_getTypeName()完全相同,不同之处在于它还适用于AnyClass,例如在Playground中输入

class MyClass {}

toString(MyClass.self) // evaluates to "__lldb_expr_49.MyClass"

5

目前苹果似乎没有将Swift反射API作为高优先级任务。但是除了@stevex的答案之外,标准库中还有另一个函数可以帮助。

从beta 6版开始,_stdlib_getTypeName可以获取变量的类型名称。将以下内容复制到空白playground中:

import Foundation

class PureSwiftClass {
}

var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"

println( "TypeName0 = \(_stdlib_getTypeName(myvar0))")
println( "TypeName1 = \(_stdlib_getTypeName(myvar1))")
println( "TypeName2 = \(_stdlib_getTypeName(myvar2))")
println( "TypeName3 = \(_stdlib_getTypeName(myvar3))")

输出结果如下:
TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS

Ewan Swick的博客文章有助于解密这些字符串:

例如,_TtSi代表Swift的内部Int类型。

Mike Ash有一篇很棒的博客文章涵盖了同样的主题


@aleclarson 是的,那也非常有用。 - Klaas
1
这些不是私有API吗?如果使用它们,苹果会批准该应用程序吗? - Eduardo Costa
@EduardoCosta 当然。它们是私有的。我只在调试构建中使用它们。 - Klaas
以下是Ewan Swick博客文章的更新链接:http://www.eswick.com/2014/06/08/Inside-Swift/ - RenniePet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接