strong、weak和unowned的区别

编写代码时需注意是否产生了循环引用,因此就产生了什么时候使用weakunowned问题?这篇文章将介绍 Swift 中的strongweakunowned的区别。

1. ARC

自动引用计数(即 Automated Reference Count,简称 ARC)是 Xcode 4.2版本的新特性,其与手动管理内存使用了相同的计数系统。不同点在于:系统在编译时会帮助我们插入合适的内存管理方法,保留和释放都会自动进行,避免了手动管理引用计数的一些潜在问题。

Swift 使用自动引用计数跟踪、管理app的内存。通常情况下,这意味着ARC会自动管理内存,开发者无需关注内存管理。当类的实例不再使用时,ARC会自动释放其占用的内存。

为帮助管理内存,ARC 有时需了解类之间的关系。在 Swift 中使用 ARC 与在 Objective-C 中使用 ARC 类似。

引用计数只适用类的实例。结构体和枚举是值类型,不是引用类型,存储和传递的时候并非使用引用。

2. strong

strong指针通过增加指向对象的引用计数,保护被指向对象不被ARC释放。即,只要有一个强指针指向该对象,它就不会被释放。

Swift 中声明的属性默认是strong。当对象间引用关系是线性时,使用strong指针不会产生问题。

当两个实例使用强指针指向彼此时,两个实例引用计数都不会变为零,即产生循环引用(strong reference cycle)。

下面是一个循环引用的示例:

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    
    var apartment: Apartment?
    deinit {
        print("\(name) is being deinitizlized")
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    
    var tenant: Person?
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

上面定义了两个类:PersonApartment,代表住户和公寓。两个类都实现了deinitializer方法,当类的实例销毁时进行打印,方便观察实例占用的内存是否释放了。

下面代码定义了两个可选类型的变量,初始值为nil。并为其分配两个新的实例:

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

目前,john变量强引用Person实例,unit4A变量强引用Apartment实例,如下所示:

现在连接两个实例。person持有apartmentapartment持有person

john?.apartment = unit4A
unit4A?.tenant = John

连接两个实例后,引用关系如下:

Person的实例强引用了Apartment的实例,Apartment的实例强引用了Person的实例,即产生了循环引用。当移除johnunit4A的引用时,实例的引用计数不会变为零,也就是实例内存不会被ARC释放。

john = nil
unit4A = nil

设置johnunit4A变量为nil后,引用关系如下:

PersonApartment实例之间的强引用无法破除。

3. 解决循环引用

Swift 提供了两种解决循环引用的方案:weakunownedweakunowned引用其它实例时不会产生强引用,引用计数不会加一。因此,不会产生循环引用。

当一个实例的生命周期短于另一个时(即一个实例可以先被销毁),使用weak引用。在上面公寓的示例中可能出现公寓没有住户的情况。因此,可以使用weak解决循环引用问题。当另一个实例生命周期与当前实例相同,或长于当前实例时,使用unowned引用。

3.1 weak引用

weak引用不会强持有引用的实例,也就不会阻止ARC释放实例。通过在声明属性、变量前添加weak关键字的方式使用弱引用。

当实例被销毁时,ARC 会自动设置弱指针为nil。由于弱指针在运行时可能被设置为nil,弱指针应被声明为可选类型的变量,而非常量。

和其它可选类型一样,可以检查弱引用值是否存在,这样就不会得到一个无效实例。

设置弱引用为nil时,不会调用属性观察器。

使用weak修饰之前实例Apartment中的tenant属性,更新后如下:

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
    
    var apartment: Apartment?
    deinit {
        print("\(name) is being deinitizlized")
    }
}

class Apartment {
    let unit: String
    init(unit: String) {
        self.unit = unit
    }
    
    // 使用weak修饰
    weak var tenant: Person?
    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

下图是实例间引用关系:

Person实例强引用Apartment实例,但Apartment实例没有强引用Person实例。当移除john实例对Person的强引用,Person实例就没有被强引用了,也就可以被销毁了。

3.2 unowned

weak一样,unowned指针也不会对指向的对象产生强引用,但unowned用在另一个实例生命周期一样或更长的情况。通过在声明属性、变量前添加unowned关键字的方式使用unowned

weak不同,unowned修饰的引用永远不为空。因此,标记为unowned的值不是可选类型,ARC 也不会将unowned引用设置为nil

只有确信引用不会被释放的时候才使用unowned,使用unowned修饰的对象被销毁后再次访问会产生运行时错误。

现在定义两个类:CustomerCreditCardCustomer是银行的客户,CreditCard是该客户的银行卡。CustomerCreditCard类都有一个属性持有彼此,这种持有关系会产生强引用。

CustomerCreditCard的关系与PersonApartment的关系稍有不同。Customer可能持有CreditCard,也可能不持有CreditCard;但CreditCard不会脱离Customer而存在。即Customer有一个可选类型的card属性,CreditCard有一个 unowned 的customer属性。

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    
    deinit {
        print("Card #\(number) is being deinitialized")
    }
}

下面创建Customer实例,并使用该实例创建CreditCard,如下所示:

var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)

其引用关系如下:

Customer实例强引用CreditCard实例,CreditCard实例 unowned Customer实例。

john变量取消对实例的强引用后,就没有强引用指向该实例,该实例就会被销毁。该实例销毁后,没有强引用指向CreditCard,其也会被销毁。

上面的示例介绍了如何使用 safe unowned 引用,Swift 同时提供了 unsafe unowned 引用,其可以避免 runtime 的安全检查,提高性能。使用 unsafe 相关操作时,开发者需自行检查其是否存在,确保安全。

使用unowned(unsafe)标记 unsafe unowned 引用。当实例销毁后,再次访问实例会直接访问销毁前的内存地址。

参考资料:

  1. What is the difference in Swift between 'unowned(safe)' and 'unowned(unsafe)'?
  2. Automatic Reference Counting

欢迎更多指正:https://github.com/pro648/tips

本文地址:https://github.com/pro648/tips/blob/master/sources/strong%E3%80%81weak%E5%92%8Cunowned%E7%9A%84%E5%8C%BA%E5%88%AB.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章