Swift学习笔记-动态特性
我们知道 Objective-C 的最显著的特性就是强大的运行时能力,这使得 Objective-C 几乎是完全灵活的一本语言。强大的自省,反射,运行时的消息派发,KVC,KVO 都让这个有着几十年历史的语言依然焕发着生机。Swift 作为一门现代化的编程语言,是十分安全的语言。绝大多数的内容都是在编译期就已经决定了的。这使得 Swift 的运行时灵活性有所折扣。但需求总是千变万化的,运行时特性往往能起到四两拨千斤的强大作用,所以 Swift 还是引入了一些动态特性,我们来逐一看一下。
获取对象类型
在 Objective-C 中我们可以很容易地做到这件事,只要使用 -class
方法就可以轻松地获取到到对象的类,使用 NSStringFromClass
方法还可以将类的名称转换成字符串。
Swift 中分为两种情况来考虑,第一种情况是 NSObject 的子类,另一种情况是纯 Swift class
NSObject 子类
对于 NSObject 子类来讲,其实它们类的信息的存储方式和在 Obj-C 中并没有什么变化,只是没有了 -class
方法供你直接使用了而已。所以这时候可以使用 Objective-C 运行时(runtime)来获取类的信息:
1 | let name = object_getClass(NSDate()) |
这里使用的其实是 Objective-C runtime 中的方法 object_getClass
这个方法接受一个 AnyObject! 参数,并返回 AnyClass! 类型。也就是说这个方法可以接受 nil 作为参数并返回一个 nil。
纯 Swift class
这里需要使用 dynamicType
来获取类型,值得一提的是在 Swift 1.2 版本之后,我们就也可以对 NSObject 子类使用 dynamicType
来获取类型了。这样的话,在 Swift 中获取对象类型的方法就变得统一起来。
Selector
在 Objective-C 中我们可以通过 selector 做很多事情,包括询问对象是否接受某个方法,指定通知调用的方法,设定 target-action 等等都是由 selector 来完成的。在 Obj-C 中我们可以很方便的使用 @selector 创建 selector 或者 NSSelectorFromString 来根据字符串动态创建 selector。Swift 中不再提供 @selector
我们只能够通过字符串来创建 Selector。
原来的 SEL 在 Swift 中对应的是一个 Selector 结构体,它提供了只接受一个字符串的初始化方法。
1 | let selector = Selector("test") |
Swift 中的 Selector 实现了 StringLiteralCovertible
协议,所以我们可以直接使用字符串赋值来完成创建 Selector 的工作。
1 | self.performSelector("test") |
注意:
在 Swift 中,如果 selector 对应的方法是被 private 修饰的话(也就是说这个方法仅在 Swift 中可见),在运行时会寻找不到对应的方法,报出unrecongnized selector
错误。这时需要在 private 前面加上 @objc 关键字来修饰。这样在运行时就可以找到相应的方法了。
1 | @objc private func test() |
当第一个参数有外部变量的话,需要在方法名和第一个外部参数之间加上 with
:
1 | func test(external name:String) |
动态创建对象
Objective-C 可以直接使用字符串或者 Class 来创建对象实例,但是 Swift 中尚未发现有这样的方法。
反射
反射是一种在运行时检测,访问或者修改类型的的行为的特性。在 Objective-C 中一般很少提及“反射”这个词,更多的是“运行时”(runtime)这种说法。这是因为 Objective-C 能够提供的运行时特性比一般的反射要强大得多。对于NSObject 子类来讲,使用 Swift 的时候也可以无缝地使用这些特性。如果抛开这些 Objective-C 的特性的话,纯 Swift 虽然也提供了一些反射的内容,但是相对要弱得多。以下内容均是基于 Swift2.2 环境:
Swift 中定义的类型都实现了 _Reflectable
接口,这个接口提供了一个 getMirror
方法用于获取实现了 MirrorType
接口的镜像,这个镜像对象包含了类内部基本的信息。开发者需要通过 reflect 方法来获取这个对象。进而在不了解对象类型的情况下对其内部进行操作。
_reflect
可以作用于所有的类型,下面举一个例子:
1 | class superapple : NSObject { |
我们发现 MirrorType
类型的对象中包含了所有的属性,我们可以通过下标来访问这些属性,每一个下标对应着一个元组(Tuple),每个元组的第一位表示属性的名称,第二位是封装了属性的值的一个 Mirror 类型,值需要通过 summary 或者 MirrorType 的 value 来访问。
现时的 Swift 版本中反射功能仍然很弱,我们只可以读取属性的值,但是并不能设置属性的值。希望之后的版本可以提供类似 KVC 的强大反射特性。
自省(Introspection)
Swift 的类型绝大多数都是在编译期就已经确定的,但是实际中有时候还是需要在运行时来判断对象属于什么类型,这时可以使用 is
运算符来进行判断,is
相当于 Objective-C 中的 isKindOfClass
方法,用于判断一个对象是否属于一个类或者他的子类。不同点在于,is
不但可以用于 class 类型,也可以用于 struct ,enum 甚至基本类型的判断。用法如下:
1 | class Test { |
是就返回 true,不是就会返回 false。
但是注意的一点是,编译器会检查类型判定的必要性,没有必要的判定会被编译器发出警告。比如如下代码:
1 | class Test { |
KVC
纯 Swift 中并没有提供 KVC 的功能,不过所有继承自 NSObject 的子类还是可以直接使用 KVC 的,因为 NSObject 有 NSKeyValueCoding 扩展。使用方法也大同小异,基本上只是语法变形而已。
KVO
同 KVC 一样,纯 Swift 并没有提供 KVO 的功能,使用 KVO 仍然是基于 NSObject 子类对象的,实质上 KVO 和 KVC 依然是基于 Objective-C 运行时实现的,这一点也无可非议。在 Swift 中使用 KVO 需要将 观察的属性使用 dynamic 来修饰。
Swift 中使用 KVO 面临的阻碍较多,我们只能观测被 dynamic 修饰的属性,而往往第三方框架中的属性没有这个修饰,或者我们无法修改使用的源码,这时就需要继承这个类并且使用 dynamic 重写想要观察的属性。
对于纯 Swift 类型来讲,由于实现原理的问题,暂时没有对其实现 KVO 的办法。目前只能使用属性观察来做替代了。
1 | class TestForKVC:NSObject{ |
方法调配(Method Swizzing)
对于 NSObject 子类,使用方法和 Objective-C 中基本类似,只是对语法做一些变形。对于纯 Swift 类型来讲,如果想要使用 Swizzle 就需要在原来的方法和替换的方法前面都加上 dynamic
标记以声明他们使用动态派发机制。
虽然 Swift 的 Method Swizzing 完全依赖了 Objective-C 运行时,但是仍然有一个的第三方实现了完全不借助 Objective-C 运行时来实现了 Method Swizzing,详见 SWRoute,他利用了一些内存的 hack 手段,利用 Swift 封装过的类似函数指针的东西来实现了方法调配。
关联对象(Associated Object)
Swift 中仍然可以使用关联对象。仍然是对原有方法进行语法变形即可。