自己实现swift lazy关键字效果
Lazy
lazy关键字的作用是在第一次使用属性的时候才去生成,而不是在一开始就初始化好,按需使用。
当计算属性的值比较耗时,或者需要外部值的依赖时,用lazy比较合适。
struct Animal {
lazy var name: String = {
// ...
return ...
}()
}
lazy必须配合var使用,因为let需要有个初始值。
当使用lazy属性时,struct对象必须声明为var,因为在访问属性时会改变该对象。如果像下面这样,会报错。
let animal = Animal()
print(animal.name)
// error: Cannot use mutating getter on immutable value: 'animal' is a 'let' constant.
global和static修饰的属性都是lazy的。
自己实现lazy
知道了lazy代表的意思,我们可以自己实现lazy。主要思路就是判断该值是否计算过,如果没有,就计算,返回结果,如果已经计算过了,就直接返回结果。下面将会用到enum关联值和简单的模式匹配。
定义
首先来定义enum
private enum LazyValue<T> {
case NotComputed(() -> T)
case Computed(T)
}
如果未计算过,就用func计算,并且将result关联到Computed(T)中。
class LazyBox<T> {
init(computation: () -> T) {
_value = .NotComputed(computation)
}
private var _value: LazyValue<T>
var value: T {
switch self._value {
case .NotComputed(let computation):
let result = computation()
self._value = .Computed(result)
return result
case .Computed(let result):
return result
}
}
}
这里利用了一个中间值_value
来存储其状态,当要真正获取value的时候,判断_value
关联属性,然后进行处理。
Test
var counter = 0
let box = LazyBox<Int> {
counter++
return counter * 10
}
assert(box.value == 10)
//assertion failed
assert(box.value == 20)
assert(counter == 1)
LazyBox所关联的函数只会执行一次,所以assert(box.value == 20)
不成立。
小优化
每次取值都是box.value,显得有些不太方便,所以我们再提供一个直接属性来获取value。
struct Animal {
private let _name = LazyBox<String> {
return "animal"
}
var name: String {
return _name.value
}
}
let animal = Animal()
animal.name
并发
如果在多线程情况下,在计算value时是不安全的。有可能出现计算多次的情况。为了解决这个问题,可以用同步队列,一次只能一个线程访问,保证只会计算一次。
class LazyBox<T> {
private var _value: LazyValue<T>
private let queue = dispatch_queue_create("LazyBox", DISPATCH_QUEUE_SERIAL)
init(computation: () -> T) {
_value = .NotComputed(computation)
}
var value: T {
var returnValue: T? = nil
dispatch_sync(queue) {
switch self._value {
case .NotComputed(let computation):
let result = computation()
self._value = .Computed(result)
returnValue = result
case .Computed(let result):
returnValue = result
}
}
return returnValue!
}
}
这可能会有点性能下降,因为在读取的时候同样也是在同步队列中读取,但是影响不会很大。
苹果文档中也说道,lazy进行计算时是线程不安全的。
Note: If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.