本文GitHub地址:GitHub
---------------------
“懒”起来
今天我们来看下怎么通过“懒”来更有效率。
具体的说,我们会讨论变量懒加载和序列的懒加载。还有喵。
问题
如果你开发了一个聊天app并且想要使用头像来代表用户。每个头像你可能有几种分辨率,我们使用以下方案来完成:
extension UIImage {
func resizedTo(size: CGSize) -> UIImage {
/* Some computational-intensive image resizing algorithm here */
}
}
class Avatar {
static let defaultSmallSize = CGSize(width: 64,height: 64)
var smallImage: UIImage
var largeImage: UIImage
init(largeImage: UIImage) {
self.largeImage = largeImage
self.smallImage = largeImage.resizedTo(Avatar.defaultSmallSize)
}
}
合理的解决方案
在Objective-C中,(应对)这种情况有一个技巧是,我们经常会使用一个中间的(intermediate)私有变量,这个技巧转换为swift版是下面这样:
class Avatar {
static let defaultSmallSize = CGSize(width: 64,height: 64)
private var _smallImage: UIImage?
var smallImage: UIImage {
get {
if _smallImage == nil {
_smallImage = largeImage.resizedTo(Avatar.defaultSmallSize)
}
return _smallImage! //
}
set {
_smallImage = newValue
}
}
var largeImage: UIImage
init(largeImage: UIImage) {
self.largeImage = largeImage
}
}
这确实是我们需要的,但是这也需要写太多代码。设想一下,如果我们想要这个方案适用于超过两种的所有不同分辨率!
Swift懒加载
class Avatar {
static let defaultSmallSize = CGSize(width: 64,height: 64)
lazy var smallImage: UIImage = self.largeImage.resizedTo(Avatar.defaultSmallSize)
var largeImage: UIImage
init(largeImage: UIImage) {
self.largeImage = largeImage
}
}
- 如果我们在
smallImage
属性赋值前访问它,只会计算默认值并返回。之后如果我们再次访问这个属性,这个属性已经被计算一次会返回已存储的值。 - 如果我们在访问之前对
smallImage
设置值,则通过密集计算生成默认值(这个过程)可以避免,会返回我们设置的额外值。 - 如果我们从未访问
smallImage
属性,这个默认值同样不会被计算。
所以这是一个很棒并且很轻松的办法来避免无用的初始化,而且同样提供了默认值,没有使用中间私有变量。
使用闭包初始化
class Avatar {
static let defaultSmallSize = CGSize(width: 64,height: 64)
lazy var smallImage: UIImage = {
let size = CGSize(
width: min(Avatar.defaultSmallSize.width,self.largeImage.size.width),height: min(Avatar.defaultSmallSize.height,self.largeImage.size.height)
)
return self.largeImage.resizedTo(size)
}()
var largeImage: UIImage
init(largeImage: UIImage) {
self.largeImage = largeImage
}
}
因为这是懒加载属性,你可以在这里使用self(记住即使你像之前示例的那样不使用闭包,也是可以使用self关键字的)。
实际上这个属性是懒加载的,意味着默认值会延迟计算,在这个时候,self已经被完全初始化了,这就是为什么在这里能够访问self-这和当你给不是懒加载的属性设置默认值的时候相反,(非懒加载属性)是在初始化时候被赋值。
懒加载常量?
在Swift中,你不能创建实例的
lazy let
属性来提供只有访问时才计算的常量。
// Global variable. Will be created lazily (and in a thread-safe way)
let foo: Int = {
print("Global constant initialized")
return 42
}()
class Cat {
static let defaultName: String = {
print("Type constant initialized")
return "Felix"
}()
}
@UIApplicationMain
class AppDelegate: UIResponder,UIApplicationDelegate {
func application(application: UIApplication,didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
print("Hello")
print(foo)
print(Cat.defaultName)
print("Bye")
return true
}
}
上面的这些代码会先打印 hello ,然后打印
Global constant initialized
和42
, 之后打印Type constant initialized
和Felix
,再打印Bye
,表明foo
和Cat.defaultName
常量是只有访问时才被创建的,不是在此之前(创建)3。
另一个例子:序列
func increment(x: Int) -> Int {
print("Computing next value of \(x)")
return x+1
}
let array = Array(0..<1000)
let incArray = array.map(increment)
print("Result:")
print(incArray[0],incArray[4])
在上面的代码中,即使在我们访问
incArray
的值之前,所有的输出值已经计算过了。所以在print("Result:")
执行之前你会看到1000行Computing next value of …
打印出来。即使我们只不关心其他值,只访问[0]
和[4]
这两个条目…想象一下我们如果不用像increment
这样的简单函数而是用计算密集型函数!
懒加载序列
让我们使用另外一种懒加载来改写上面的代码。
在swift标准库中,
SequenceType
和CollectionType
协议 有一个名为lazy
的计算属性,它们分别返回特殊的LazySequence
或LazyCollection。
这些类型是map、
flatMap
、filter等
类似的高阶函数专用的,通过“lazy“关键词使用5。
让我们在实例中看一下它是怎么工作的吧:
let array = Array(0..<1000)
let incArray = array.lazy.map(increment)
print("Result:")
print(incArray[0],incArray[4])
现在代码的打印如下:
Result:
Computing next value of 0…
Computing next value of 4…
1 5
这样效率更高!特别是大的序列(如拥有1000个元素的)和计算密集的闭包情况下,更加的明显。6
链式懒加载序列
func double(x: Int) -> Int {
print("Computing double value of \(x)…")
return 2*x
}
let doubleArray = array.lazy.map(increment).map(double)
print(doubleArray[3])
这只会在条目被访问时计算
double(increment(array[3]))
,而不是在访问它之前,并且只计算被访问的数据。
相反,使用
array.map(increment).map(double)[3]
(不使用lazy
)会先计算array所有的输出值,当所有的输出值被计算完毕后,取出第四个。但是更糟糕是,它会遍历array两次,每次调用map都会遍历!这将会是对计算时间的极大浪费!
结论
“懒”起来7。
1.在swift mail lists 仍有关于如何修复并支持懒加载常量一些讨论,但是目前为止对于swift2是这样的做法