谈谈 Swift中的 lazy 属性

1.OC中的懒加载:

在OC中,为了提高程序的性能,经常使用懒加载来加载一些诸如读取文件,分配内存,加载内容非常复杂的内容等耗时耗内存的操作. 如在控制器中延迟加载一个模型,通常这样重写该getter:

1
@interface ViewController ()
2
@property (strong, nonatomic) PersonInfo *personInfo;
3
@end
4
 
5
@implementation ViewController
6
 
7
- (PersonInfo *)personInfos{
8
    if (!_personInfo) {
9
        _personInfo = [[PersonInfo alloc] init];
10
        NSLog(@"此处有耗性能操作");
11
    }
12
    return _personInfo;
13
}

初始化控制器对象后, _personInfo为nil,等到第一次要使用到该属性时才调用其getter来创建,而以后要使用则不再创建对象,而是直接返回该属性.

2. Swift中的懒加载:

在swift中可用关键字lazy来修饰struct或class的成员变量,从而实现懒加载的效果:

1
import UIKit
2
3
class Person {
4
}
5
6
class ViewController: UIViewController {
7
    
8
    lazy var person:Person = {
9
        var tmpPerson = Person()
10
        print("此处有耗性能操作")
11
        return tmpPerson
12
    }()
13
    
14
    override func viewDidLoad() {
15
        super.viewDidLoad()
16
        someFunc()
17
        someFunc()
18
    }
19
20
    func someFunc(){
21
        print(person)
22
    }
23
}
24
//此处有耗性能操作
25
//LazyTest.Person
26
//LazyTest.Person

闭包在第一次调用的时候调用,之后就不调用了.当然这些耗性能的操作也可以在类的构造方法中执行:

1
import UIKit
2
3
class Person {
4
    init(){
5
        print("此处有耗性能操作")
6
    }
7
}
8
9
class ViewController: UIViewController {
10
    
11
    lazy var person = Person()
12
    
13
    override func viewDidLoad() {
14
        super.viewDidLoad()
15
        someFunc()
16
        someFunc()
17
    }
18
    
19
    func someFunc(){
20
        print(person)
21
    }
22
}

所以lazy可以简洁方便的修饰属性,适用情景:

(1) 属性开始时还不确定是什么或者是否会被用到.
(2) 属性需要复杂的计算:会消耗大量的CPU.
(3) 属性只需要初始化一次时.

需要注意的是:

(1) 被lazy修饰的属性(如这里的person)要用var来声明,因为该属性在类(如这里的ViewController)的实例初始化完成前还不能拥有一个值,而且访问该属性是一个mutating操作,所以包含该属性的struct或class也应该是可被修改的.而用let修饰的全局常量或者类属性(type property)却默认是lazy的(也是线程安全的).

(2) 并发性问题:如官方文档中提示:

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.

即在多线程操作中, 被lazy修饰的属性可能被多个线程访问,则只初始化一次就不能保证了.目测替代方案有二:可以放在dispatch_once中初始化,或者像< Lazy Properties in Structs >那样在属性创建前根据判断是否已存在而作不同的操作.

1
import UIKit
2
3
class Person {
4
}
5
6
var name:String?
7
8
class ViewController: UIViewController {
9
    
10
    lazy var person:Person = {
11
        var tmpPerson = Person()
12
        print("此处有耗性能操作")
13
        return tmpPerson
14
    }()
15
16
    override func viewDidLoad() {
17
        super.viewDidLoad()
18
        
19
        for _ in 1...3{
20
            dispatch_async(dispatch_queue_create("com.yg", DISPATCH_QUEUE_CONCURRENT), {
21
                self.someFunc()
22
            })
23
        }
24
    }
25
    
26
    func someFunc(){
27
        print(person)
28
    }
29
}
30
//此处有耗性能操作
31
//此处有耗性能操作
32
//此处有耗性能操作
33
//LazyTest.Person
34
//LazyTest.Person
35
//LazyTest.Person

3. lazy的其他用法:

在对集合类型(如Array)使用高阶函数(如map)时,所有的结果会在第一次访问前就计算出来,即使我们只访问其中的一个元素:

1
class ViewController: UIViewController {
2
    override func viewDidLoad() {
3
        super.viewDidLoad()
4
        
5
        let array = Array(0..<100)
6
        
7
        let result = array.map{
8
            (i: Int) -> Int in
9
            print("访问前的准备: \(i)")
10
            return i * 2
11
        }
12
        print("准备访问数组最后一个元素")
13
        print("最后一个元素是: \(result[99])")
14
        print("访问完毕")
15
16
    }
17
}
18
//...
19
//访问前的准备: 96
20
//访问前的准备: 97
21
//访问前的准备: 98
22
//访问前的准备: 99
23
//准备访问数组最后一个元素
24
//最后一个元素是: 198
25
//访问完毕

这无疑不是我们想要的. 这时lazy就派上用场了!
在这些集合类中有个名为 lazy 的属性,它包含的内容与所在的集合类对象一样,并且可以对 “map”,”filter” 等操作延迟实现。所以这样使用lazy可以大大优化性能:

1
class ViewController: UIViewController {
2
    override func viewDidLoad() {
3
        super.viewDidLoad()
4
        
5
        let array = Array(0..<100)
6
        
7
        let result = array.lazy.map{
8
            (i: Int) -> Int in
9
            print("访问前的准备: \(i)")
10
            return i * 2
11
        }
12
        print("准备访问数组最后一个元素")
13
        print("最后一个元素是: \(result[99])")
14
        print("访问完毕")
15
16
    }
17
}
18
//准备访问数组最后一个元素
19
//访问前的准备: 99
20
//最后一个元素是: 198
21
//访问完毕

参考:

LAZY 修饰符和 LAZY 方法

Lazy Initialization with Swift

Lazy Properties in Structs

The Swift Programming Language

Being Lazy