定义

  • 跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)
  • Swfit的ARC中有3各引用
    • 强引用(Strong reference):默认情况下,引用都是强引用
    • 弱引用(weak reference):通过weak定义弱引用
      • 必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为Nil
      • ARC自动给弱引用设置nil时,不会触发属性观察器
    • 无主引用(unowned reference):通过unowned定义无主引用
      • 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_unretained)
      • 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
      • Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated

weak、unowned的使用限制

  • weak、owned只能用在类实例上面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    protocol Livable : AnyObject {}
    class Person {}

    weak var p0: Person?
    weak var p1: AnyObject?
    weak var p2: Livable?

    unowned var p10: Person?
    unowned var p11: AnyObject?
    unowned var p12: Livable?

Autoreleasepool

1
2
3
4
5
6
public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result

autoreleasepool {
let p = MJPerson(age: 20, name: "Jack")
p.run()
}

循环引用(Reference Cycle)

  • weak、unowned 都能解决循环引用的问题, unowned 要比 weak 少一些性能消耗
  • 在生命周期中可能会变为 nil 的使用 weak
  • 初始化赋值后再也不会变为 nil 的使用 unowned

闭包的循环引用

  • 闭包表达式默认会用到的外层对象产生额外的强引用(对外层对象进行了retain操作)

  • 正面代码会产生循环引用,导致Person对象无法释放(看不到Persion的deinit被调用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Person {
    var fn: (() -> ())?
    func run() { print("run") }
    deinit { print("deinit") }
    }

    func test() {
    let p = Person()
    p.fn = { p.run() }
    }
    test()
  • 在闭包表达式的捕获列表声明weak或unowned引用,解决循环引用问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    p.fn = {
    [weak p] in
    p?.run()//用weak修饰时,要用p?,可选
    }

    p.fn = {
    [unowned p] in
    p.run()//用unowned修饰是,用p, 非可选
    }

    p.fn = {
    [weak wp = p, unowned up = p, a = 10 + 20] in
    wp?.run()
    }
  • 如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的(因为在实例初始化完毕之后才能引用self)

  • 下面闭包fn内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出 self

    1
    2
    3
    4
    5
    6
    7
    8
    class Person {
    lazy var fn: (() -> ()) = {
    [weak self] in
    self?.run()
    }
    func run() { print("run") }
    deinit { print("deinit") }
    }
  • 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Person {
    var age: Int = 0

    lazy var getAge: Int = {
    self.age
    }()//这个属性带有()执行方法

    deinit { print("deinit") }
    }

@escaping

  • 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
  • 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
  • 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过 @escaping 声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import Dispatch
    typealias Fn = () -> ()

    // fn是非逃逸闭包
    func test1(_ fn: Fn) { fn() }

    // fn是逃逸闭包
    var gFn: Fn?
    func test2(_ fn: @escaping Fn) { gFn = fn }

    // fn是逃逸闭包
    func test3(_ fn: @escaping Fn) {
    DispatchQueue.global().async {
    fn()
    }
    }

    class Person {
    var fn: Fn
    // fn是逃逸闭包
    init(fn: @escaping Fn) {
    self.fn = fn
    }
    func run() {
    // DispatchQueue.global().async也是一个逃逸闭包
    // 它用到了实例成员(属性、方法),编译器会强制要求明确写出self
    DispatchQueue.global().async {
    self.fn()
    }
    }
    }

逃逸闭包的注意点:
逃逸闭包不可以捕获inout参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typealias Fn = () -> ()
func other1(_ fn: Fn) { fn() }
func other2(_ fn: @escaping Fn) { fn() }

func test(value: inout Int) -> Fn {
other1 { value += 1 }

// error: 逃逸闭包不能捕获inout参数
//other2 { value += 1 }

func plus() { value += 1 }
// error: 逃逸闭包不能捕获inout参数
return plus
}

参考

  • 李明杰老师课件