lazy 修饰符和 lazy 方法
在 Swift 中我们使用在变量属性前加 lazy
关键字的方式来简单地指定延时加载。
class ClassA {
lazy var str: String = {
let str = "Hello"
print("只在首次访问输出")
return str
}()
lazy var name: String = "Hello guys"
lazy var age: Int = {
let age = 15
return age
}()
}
我们在使用 lazy
作为属性修饰符时,只能声明属性是变量。另外我们需要显式地指定属性类型,并使用一个可以对这个属性进行赋值的语句来在首次访问属性时运行。如果我们多次访问这个实例的 str
属性的话,可以看到只有一次输出。
为了简化,我们如果不需要做什么额外工作的话,也可以对这个 lazy
的属性直接写赋值语句:
lazy var str: String = "Hello"
另外一个不太引起注意的是,在 Swift 的标准库中,我们还有一组 lazy
方法,它们的定义是这样的:
func lazy<S : SequenceType(s: S)> -> LazySequence<S>
func lazy<S : CollectionType where S.Index : RandomAccessIndexType>(s: S) -> LazyRandomAccessCollection<S>
func lazy<S : CollectionType where S.Index : BidirectionalIndexType>(s: S) -> LazyBidirectionalCollection<S> // Bidirectional: [ˌbaɪdəˈrekʃənl] 双向的
func lazy<S : CollectionType where S.Index : ForwardIndexType>(s: S) -> LazyForwardCollection<S>
这些方法可以配合像 map
或是 filter
这类接受闭包并进行运行的方法一起,让整个行为变成延时进行的。在某些情况下这么做也对性能会有不小的帮助。例如,直接使用 map
时:
let data = 1...3
let result = data.map {
(i: Int) -> Int in
print("正在处理 \(i)")
return i * 2
}
print("准备访问结果")
for i in result {
print("操作后的结果为 \(i)")
}
print("操作完毕")
输出结果为:
正在处理 1
正在处理 2
正在处理 3
准备访问结果
操作后的结果为 2
操作后的结果为 4
操作后的结果为 6
操作完毕
而如果我们先进行一次 lazy
操作的话,我们就能得到延时运行版本的容器:
let data = 1...3
let result = data.lazy.map {
(i: Int) -> Int in
print("正在处理 \(i)")
return i * 2
}
print("准备访问结果")
for i in result {
print("操作后的结果为 \(i)")
}
print("操作完毕")
运行结果为:
准备访问结果
正在处理 1
操作后的结果为 2
正在处理 2
操作后的结果为 4
正在处理 3
操作后的结果为 6
操作完毕
对于那些不需要完全运行,可能提前退出的情况,使用 lazy
来进行性能优化效果会非常有效。
Reflection 和 Mirror
反射 (Reflection)是一种在运行时检测、访问或者修改类型的行为的特性。一般的静态语言类型的结构和方法的调用等都需要在编译时决定,开发者能做的很多时候只是使用控制流 (比如 if 或者 switch) 来决定做出怎样的设置或是调用哪个方法。而反射特性可以让我们有机会在运行的时候通过某些条件实时地决定调用的方法,或者甚至向某个类型动态地设置甚至加入属性及方法,是一种非常灵活和强大的语言特性。
Swift 中所有的类型都实现了 Reflectable
,这是一个内部协议,我们可以通过 _reflect
来获取任意对象的一个镜像,这个镜像对象包含类型的基本信息,在 Swift 2.0 之前,这是对某个类型的对象进行探索的一种方法。在 Swift 2.0 中,这些方法已经从公开的标准库中移除了,取而代之, 我们可以使用 Mirror
类型来做类似的事情:
struct Person {
let name: String
let age : Int
}
let xiaoMing = Person(name: "xiaoMing", age: 16)
let r = Mirror(reflecting: xiaoMing)
print("xiaoMing 是 \(r.displayStyle!)") //xiaoMing 是 struct
print("属性个数:\(r.children.count)") //属性个数:2
for child in r.children {
print("属性名:\(String(describing: child.label)), 值:\(child.value)
}
//属性名:Optional("name"), 值:xiaoMing
//属性名:Optional("age"), 值:16
通过 Mirror
初始化得到的结果中包含的元素的描述都被集合在 children
属性下,如果你有心可以到 Swift 标准库中查找它的定义,它实际上是一个 Child
的集合,而 Child
则是一对键值的多元组:
public typealias Child = (label: String?, value: Any)
public typealias Children = AnyCollection<Mirror.Type.Child>
AnyCollection
是遵守 CollectionType
协议的,因此我们可以简单地使用 count
来获取元素的个数,而对于具体的代表属性的多元组,则使用下标进行访问。在对于我们的例子中,每个Child 都是具有两个元素的多元组,其中第一个是属性名,第二个是这个属性所存储的值。需要特别注意的是,这个值有可能是多个元素组成嵌套的形式 (例如属性值是数组或者字典的话,就是这样的形式)。
如果觉得一个个打印太过于麻烦,我们也可以简单地使用 dump
方法来通过获取一个对象的镜像并进行标准输出的方式将其输出出来。比如对上面的对象 xiaoMing
:
dump(xiaoMing)
//输出
// ▿ Tips14.Person
// - name: "xiaoMing"
// - age: 16
对于一个从对象反射出来的 Mirror
,它所包含的信息是完备的。也就是说我们可以在运行时通过 Mirror
的手段了解一个 Swift 类型 (当然 NSObject 类也可以) 的实例的属性信息。该特性最容易想到的应用的特性就是为任意 model 对象生成对应的 JSON 描述。我们可以对等待处理的对象的 Mirror 值进行深度优先的访问,并按照属性的 valueType
将它们归类对应到不同的格式化中。
另一个常见的应用场景是类似对 Swift 类型的对象做像 Objective-C 中 KVC 那样的 valueForKey
的取值。通过比较取到的属性的名字和我们想要取得的 key 值就行了,非常简单:
//实现Object-C中KVC那样的取值
func valueFrom(_ object:Any, key: String) -> Any? {
let mirror = Mirror(reflecting: object)
for child in mirror.children {
let (targetKey, targetMirror) = (child.label, child.value)
if key == targetKey {
return targetMirror
}
}
return nil
}
if let name = valueFrom(xiaoMing, key: "name") as? String {
print("通过key得到值:\(name)")
}
// 输出:通过key得到值:xiaoMing
在现在的版本中,Swift 的反射特性并不是非常强大,我们只能对属性进行读取,还不能对其设定,不过我们有希望能在将来的版本中获得更为强大的反射特性。另外需要特别注意的是,虽然理论上将反射特性应用在实际的 app 制作中是可行的,但是这一套机制设计的最初目的是用于 REPL 环境和 Playground 中进行输出的。所以我们最好遵守 Apple 的这一设定,只在 REPL 和 Playground 中用它来对一个对象进行深层次的探索,而避免将它用在 app 制作中 -- 因为你永远不知道什么时候它们就会失效或者被大幅改动。