Swift Runtime
我们都知道 Objective-C 是一门动态的语言,有的时候我们会使用 Runtime
处理一些在 Objective-C 上面无法实现或者很难实现的功能。例如:在扩展中添加属性;动态的获取属性的名称,方法名等。那么究竟什么是 Runtime
?
什么是 Runtime
?
Runtime
简称运行时。Objective-C 就是运行时机制,也就是在程序运行时候的一些机制,其中最主要的是消息机制。对于我们熟悉的C语言,函数的调用在编译的时候会决定调用哪个函数。但对于 Objective-C 的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
也就有了下面这两点结论:
- 在编译阶段,Objective-C 可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。
- 在编译阶段,C语言调用未实现的函数就会报错。
Swift 中的 Runtime
好了上面说了这么多都是在说 Objective-C 的 Runtime
,那么 Swift 的 Runtime
是什么样的呢?我们来写几行代码看一下。
1 | class Demo { |
猜一下,会输出什么?当然,这里什么都不输出!为什么?因为我们创建的类 Demo
是一个纯 Swift 的类,因为 Swift 是一门静态语言,所以我们对其运用 Runtime
机制当然是不可能获取到 Demo
类的属性的。如果我非要在 Swift 代码中运用 Runtime
技术呢?当然是有办法的。我们可以在你要获取的属性(函数同理)前用 @objc
修饰即可(当然加上 dynamic
也是可以的,不过编译器会提示错误,必须要在 dynamic
前添加 @objc
),就像如下这样:
1 | class Demo { |
我们在添加如下的代码来打印出方法名:
1 | var fCount: UInt32 = 0 |
输出如下:
1 | Property: name |
为什么加上 @objc
就有 Runtime
机制了呢?Swift 不是静态语言吗?
我们来看一下官方文档里对 @objc
是怎么说的。
把这个特性用到任何可以在 Objective-C 中表示的声明上——例如,非内嵌类,协议,非泛型枚举(原始值类型只能是整数),类和协议的属性、方法(包括
setter
和getter
),初始化> > 器,反初始化器,下标。objc
特性告诉编译器,这个声明在 Objective-C 代码中是可用的。给扩展应用这个特性与为这个扩展中所有不显式标记为 nonobjc 特性的成员应用是一样的效果。
用
objc
特性标记的类必须继承自一个 Objective-C 中定义的类。如果你把objc
用到类或协议中,它会隐式地应用于该类或协议中 Objective-C 兼容的成员上。如果一个类继承自另一个> 带objc
特性标记或 Objective-C 中定义的类,编译器也会隐式地给这个类添加objc
特性。标记为objc
特性的协议不能继承自非objc
特性的协议。
objc
特性同样会在下面的情况中隐式地添加:
- 声明是子类的重写,并且父类的声明有
objc
特性;- 声明满足的需求来自一个拥有
objc
特性的协议;- 声明有
IBAction
,IBOutlet
,IBDesignable
,IBInspectable
,NSManaged
, 或者GKInspectable
特性。如果你在一个枚举中使用
objc
特性,枚举名和每个成员名串联起来,作为枚举成员暴露给 Objective-C 代码。成员名首字母大写。例如,一个 SwiftPlanet
枚举成员叫做venus
,> 它作为一个叫PlanetVenus
的成员暴露到 Objective-C 代码中。
objc
特性可以接受一个特性实参,由一个标识符组成。当你想在 Objective-C 中为objc
特性标记的实体暴露一个不同的名字时,用这个特性。你可以把这个实参用在命名类,枚举,枚举成> 员,协议,方法,getter,setter,初始化器。下面的例子把 ExampleClass 中 enabled 属性的getter作为 isEnabled 暴露给 Objective-C 代码,而不仅仅是属性本身的名字。
1
2
3
4
5
6
7
8 @objc
class ExampleClass: NSObject {
var enabled: Bool {
@objc(isEnabled) get {
// Return the appropriate value
}
}
}
首先有 @objc
这个关键字,它是用来将 Swift 的 API 暴漏给 Objective-C 和 Runtime
使用的,文档里也很清楚的说明了,如果你类继承自 Objective-C 的类,这个标识符就会被自动加进去,加了这标识符的属性、方法无法保证都会被运行时调用,因为 Swift 会做静态优化,想要完全被声明成动态调用,必须使用 dynamic
标识符修饰,当然添加了 dynamic
的时候,它会自己在加上 @objc
这个标识符。
举个例子:
1 | class Demo: NSObject { |
虽然这时我们没有在 dBool
和 dInt
两个属性添加 @objc
修饰,但是 Runtime
时依然能够获取到,因为类继承了 Objective-C 中的 NSObject
,他会隐式的在属相前面添加 @objc
,同理,继承自 UIViewController
等的类,都有这个特性。