• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Swift5.4语言指南(二十六)自动引用计数(ARC)

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739923.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况。在大多数情况下,这意味着内存管理在Swift中“有效”,您无需自己考虑内存管理。当不再需要类实例使用的实例时,ARC会自动释放它们。

但是,在某些情况下,ARC需要更多有关代码部分之间关系的信息,以便为您管理内存。本章介绍了这些情况,并说明了如何启用ARC来管理应用程序的所有内存。在Swift中使用ARC与在将ARC与Objective-C一起使用过渡到ARC发行说明中描述的方法非常相似

引用计数仅适用于类的实例。结构和枚举是值类型,而不是引用类型,并且不通过引用存储和传递。

ARC如何运作

每次创建类的新实例时,ARC都会分配一块内存来存储有关该实例的信息。该内存保存有关实例类型的信息,以及与该实例关联的任何已存储属性的值。

此外,当不再需要某个实例时,ARC会释放该实例使用的内存,以便该内存可用于其他目的。这样可确保不再需要类实例时,它们不会占用内存空间。

但是,如果ARC要取消分配仍在使用的实例,则将无法再访问该实例的属性或调用该实例的方法。确实,如果您尝试访问该实例,则您的应用很可能会崩溃。

为了确保实例在仍然需要时不会消失,ARC跟踪当前引用每个类实例的属性,常量和变量的数量。只要仍存在至少一个对实例的有效引用,ARC便不会取消分配该实例。

为此,每当您将类实例分配给属性,常量或变量时,该属性,常量或变量都会强烈引用该实例。该引用被称为“强”引用,因为它可以使该实例保持牢靠,并且只要该强引用仍然存在,就不允许对其进行重新分配。

ARC在行动

这是一个有关自动引用计数工作方式的示例。本示例从名为的简单类开始,该类Person定义了称为的存储常量属性name

  1. class Person {
  2. let name: String
  3. init(name: String) {
  4. self.name = name
  5. print("\(name) is being initialized")
  6. }
  7. deinit {
  8. print("\(name) is being deinitialized")
  9. }
  10. }

Person类有一个初始化,设置该实例的name属性并打印一个消息,指示初始化正在进行中。所述Person类还具有当类的实例被释放,打印的消息的deinitializer。

下一个代码片段定义了三个type类型的变量Person?,这些变量用于Person在后续代码片段中设置对新实例的多个引用由于这些变量是可选类型(Person?,不是Person),因此它们会自动以值初始化nil,并且当前未引用Person实例。

  1. var reference1: Person?
  2. var reference2: Person?
  3. var reference3: Person?

现在,您可以创建一个新Person实例,并将其分配给以下三个变量之一:

  1. reference1 = Person(name: "John Appleseed")
  2. // Prints "John Appleseed is being initialized"

请注意,该消息将在调用类的初始化程序的位置打印这确认初始化已发生。"John Appleseed is being initialized"Person

由于Person已将实例分配给reference1变量,因此现在有了reference1对新Person实例的强引用因为至少有一个强有力的参考,所以ARC确保将其Person保留在内存中并且不会被释放。

如果将同一Person实例分配给另外两个变量,则将建立对该实例的两个更强引用:

  1. reference2 = reference1
  2. reference3 = reference1

现在,有三个强烈引用此单个Person实例。

如果通过分配nil两个变量来破坏其中两个强引用(包括原始引用),则将保留单个强引用,并且Person不会释放实例:

  1. reference1 = nil
  2. reference2 = nil

Person直到第三个和最后一个强引用中断,ARC才会取消分配实例,这时很明显您不再使用该Person实例:

  1. reference3 = nil
  2. // Prints "John Appleseed is being deinitialized"

类实例之间的强引用循环

在上面的示例中,ARC能够跟踪对Person您创建的新实例的引用数量,并在Person不再需要实例时将其取消分配

但是,可以编写这样的代码,其中类的实例永远不会达到强引用为零的程度。如果两个类实例相互之间有很强的引用,从而使每个实例使另一个实例保持活动状态,则可能发生这种情况。这被称为强参考周期

通过将类之间的某些关系定义为弱引用或无主引用而不是强引用,可以解决强引用循环问题。解决类实例之间的强引用循环中介绍了此过程但是,在学习如何解决强参考循环之前,了解这种循环是如何产生的很有用。

这是一个如何意外创建强参考循环的示例。此示例定义了两个名为Person和的Apartment,它们对一组公寓及其居民进行建模:

  1. class Person {
  2. let name: String
  3. init(name: String) { self.name = name }
  4. var apartment: Apartment?
  5. deinit { print("\(name) is being deinitialized") }
  6. }
  7. class Apartment {
  8. let unit: String
  9. init(unit: String) { self.unit = unit }
  10. var tenant: Person?
  11. deinit { print("Apartment \(unit) is being deinitialized") }
  12. }

每个Person实例都有一个nametype类型属性String和一个apartment最初为的可选属性nilapartment属性是可选的,因为一个人可能并不总是拥有公寓。

同样,每个Apartment实例都具有unit类型属性,String并具有tenant最初为的可选属性nil承租人属性是可选的,因为公寓可能并不总是有承租人。

这两个类都定义了一个反初始化器,它显示出该类的实例正在被反初始化的事实。这使您可以查看Person和的实例是否Apartment按预期进行了释放。

下一个代码片段定义了两个可选类型的变量,称为johnunit4A,这些变量将在下面设置为特定的ApartmentPerson实例。这两个变量的初始值均为nil,这是可选参数:

  1. var john: Person?
  2. var unit4A: Apartment?

现在,您可以创建一个特定的Person实例和Apartment实例,并将这些新实例分配给johnunit4A变量:

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

这是创建和分配这两个实例后强引用的外观。john现在变量对新Person实例具有强引用,而unit4A变量对新实例具有强引用Apartment

现在,您可以将两个实例链接在一起,以便此人拥有一个公寓,并且该公寓有一个租户。请注意,感叹号(!)用于解包和访问存储在johnunit4A可选变量中的实例,以便可以设置这些实例的属性:

  1. john!.apartment = unit4A
  2. unit4A!.tenant = john

将两个实例链接在一起后,以下是强引用的外观:

不幸的是,链接这两个实例会在它们之间创建强大的参考周期。Person实例现在具有很强的参考Apartment实例,该Apartment实例具有很强的参考Person实例。因此,当打破由johnunit4A变量持有的强引用时,引用计数不会降为零,并且实例也不会被ARC释放:

  1. john = nil
  2. unit4A = nil

请注意,将这两个变量设置为时,都不会调用任何反初始化程序nil强大的参考周期可防止PersonApartment实例被重新分配,从而导致应用程序中的内存泄漏。

johnandunit4A变量设置为时,以下是强引用的外观nil

Person实例与Apartment实例之间的强引用保持不变,并且不能被破坏。

解决类实例之间的强引用循环

当您使用类类型的属性时,Swift提供了两种解决强引用循环的方法:弱引用和无主引用。

弱引用和不带所有权的引用使引用周期中的一个实例可以引用另一个实例,而无需对其进行严格控制。然后,这些实例可以相互引用,而无需创建强大的引用周期。

当另一个实例的生存期较短时(即,另一个实例可以首先被释放时),请使用弱引用。Apartment上面示例中,对于公寓来说,在其生命周期中的某个时候能够没有租户是合适的,因此在这种情况下,弱引用是打破引用周期的一种适当方法。相反,当另一个实例具有相同的生存期或更长的生存期时,请使用无主引用。

参考文献薄弱

一个弱引用是不保留对实例的强抱它指的是,所以不从引用的实例处置停止ARC的参考。此行为可防止参考成为强大参考周期的一部分。您可以通过将weak关键字放在属性或变量声明之前来指示弱引用

由于弱引用不能完全控制它所引用的实例,因此有可能在弱引用仍在引用该实例时将其释放。因此,ARC将自动设置一个弱引用,以nil使其所引用的实例被重新分配。而且,由于弱引用需要允许nil在运行时将其值更改为,因此它们始终被声明为可选类型的变量,而不是常量。

您可以像其他任何可选值一样检查弱引用中是否存在值,并且永远不会以对不再存在的无效实例的引用结束。

笔记

当ARC对nil设置弱引用时,不调用属性观察器

下面的示例与上面PersonApartment示例相同,但有一个重要区别。这次,Apartment类型的tenant属性被声明为弱引用:

  1. class Person {
  2. let name: String
  3. init(name: String) { self.name = name }
  4. var apartment: Apartment?
  5. deinit { print("\(name) is being deinitialized") }
  6. }
  7. class Apartment {
  8. let unit: String
  9. init(unit: String) { self.unit = unit }
  10. weak var tenant: Person?
  11. deinit { print("Apartment \(unit) is being deinitialized") }
  12. }

像以前一样,创建了两个变量(johnunit4A的强引用以及两个实例之间的链接:

  1. var john: Person?
  2. var unit4A: Apartment?
  3. john = Person(name: "John Appleseed")
  4. unit4A = Apartment(unit: "4A")
  5. john!.apartment = unit4A
  6. unit4A!.tenant = john

将两个实例链接在一起后,这些引用的外观如下:

Person实例仍然具有对该实例的强引用Apartment,但是该Apartment实例现在具有对该实例的引用Person这意味着,当您通过将john变量设置为来破坏变量所nil拥有的强引用时,将不再有对该Person实例的强引用

  1. john = nil
  2. // Prints "John Appleseed is being deinitialized"

由于不再有对该Person实例的更强引用,因此将其释放,并将该tenant属性设置为nil

Apartment实例的唯一唯一强引用来自unit4A变量。如果您破坏了强引用,则不再有对该Apartment实例的强引用

  1. unit4A = nil
  2. // Prints "Apartment 4A is being deinitialized"

由于不再有对该Apartment实例的更强引用,因此也将其释放:

笔记

在使用垃圾回收的系统中,弱指针有时用于实现简单的缓存机制,因为没有强引用的对象仅在内存压力触发垃圾回收时才被释放。但是,使用ARC时,值将在删除最后一个强引用后立即释放,这使弱引用不适用于此目的。

无人参考

像弱引用一样,无主引用也不会对其引用的实例保持强大控制力但是,与弱引用不同,当另一个实例具有相同的生存期或更长的生存期时,将使用无主引用。您可以通过将unowned关键字放在属性或变量声明之前来指示无主引用

与弱引用不同,期望无所有权引用始终具有值。因此,将值标记为未拥有不会使其成为可选,并且ARC永远不会将未拥有的引用的值设置为nil

重要的

仅当确定引用始终引用尚未取消分配的实例时,才使用无主引用

如果在释放该实例后尝试访问一个未拥有的引用的值,则会收到运行时错误。

下面的示例定义两个类CustomerCreditCard,它们分别为银行客户和该客户的可能的信用卡建模。这两个类各自将另一个类的实例存储为属性。这种关系有可能建立一个强大的参考周期。

之间的关系CustomerCreditCard距离之间的关系略有不同Apartment,并Person在上面的弱参考例所示。在此数据模型中,客户可能拥有或可能没有信用卡,但是信用卡将始终与该客户相关联。一个CreditCard实例永远会超越的Customer,它指的是。为了表示这一点,Customer该类具有可选card属性,但是CreditCard该类具有未拥有(且非可选)customer属性。

此外,只能通过将值和实例传递给自定义初始化程序来创建CreditCard实例这样可以确保在创建实例时实例始终具有与之关联的实例。numbercustomerCreditCardCreditCardcustomerCreditCard

由于信用卡将始终有客户,因此您可以将其customer属性定义为无主引用,以避免出现强大的引用周期:

  1. class Customer {
  2. let name: String
  3. var card: CreditCard?
  4. init(name: String) {
  5. self.name = name
  6. }
  7. deinit { print("\(name) is being deinitialized") }
  8. }
  9. class CreditCard {
  10. let number: UInt64
  11. unowned let customer: Customer
  12. init(number: UInt64, customer: Customer) {
  13. self.number = number
  14. self.customer = customer
  15. }
  16. deinit { print("Card #\(number) is being deinitialized") }
  17. }

笔记

该类number属性CreditCard使用UInt64而不是类型定义Int,以确保该number属性的容量足以在32位和64位系统上存储16位卡号。

下一个代码片段定义了一个Customer名为的可选变量john,该变量将用于存储对特定客户的引用。由于该变量是可选的,因此其初始值为nil:

  1. var john: Customer?

现在Customer您可以创建一个实例,并使用它来初始化新CreditCard实例并将其分配为该客户的card属性:

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

既然您已经链接了两个实例,则引用的外观如下:

Customer实例现在具有很强的参考CreditCard实例,该CreditCard实例有一个无主的参考Customer实例。

由于无主customer引用,因此当您破坏john变量持有的强引用时,将不再有对该Customer实例的强引用

因为不再有对该Customer实例的强引用,所以将其释放。在这种情况发生之后,将不再有对该CreditCard实例的更强引用,并且也将其释放:

  1. john = nil
  2. // Prints "John Appleseed is being deinitialized"
  3. // Prints "Card #1234567890123456 is being deinitialized"

上面的最后一个代码片段显示,Customer实例和CreditCard实例的反初始化器在将john变量设置为之后都打印其“反初始化”消息nil

笔记

上面的示例说明了如何使用安全的未拥有引用。在需要禁用运行时安全检查的情况下,例如出于性能原因,Swift还提供了不安全的无主引用。与所有不安全的操作一样,您有责任检查该代码的安全性。

您通过写来表示不安全的,未拥有的引用unowned(unsafe)如果在释放引用的实例后尝试访问不安全的无主引用,则程序将尝试访问该实例以前所在的内存位置,这是不安全的操作。

无主的可选参考

您可以将对类的可选引用标记为未拥有。就ARC所有权模型而言,可以在相同的上下文中使用未拥有的可选引用和弱引用。区别在于,当您使用无主的可选引用时,您有责任确保其始终引用有效对象或将其设置为nil

这是一个跟踪学校特定部门开设的课程的示例:

  1. class Department {
  2. var name: String
  3. var courses: [Course]
  4. init(name: String) {
  5. self.name = name
  6. self.courses = []
  7. }
  8. }
  9. class Course {
  10. var name: String
  11. unowned var department: Department
  12. unowned var nextCourse: Course?
  13. init(name: String, in department: Department) {
  14. self.name = name
  15. self.department = department
  16. self.nextCourse = nil
  17. }
  18. }

Department对部门提供的每门课程都保持着强烈的参考。在ARC所有权模型中,部门拥有其课程。Course有两个无主推荐人,一个是系的,另一个是学生应修的下一门课程;一个课程不拥有这些对象中的任何一个。每个课程都是某个部门的一部分,因此该department属性不是可选的。但是,由于某些课程没有推荐的后续课程,因此该nextCourse属性是可选的。

这是使用这些类的示例:

  1. let department = Department(name: "Horticulture")
  2. let intro = Course(name: "Survey of Plants", in: department)
  3. let intermediate = Course(name: "Growing Common Herbs", in: department)
  4. let advanced = Course(name: "Caring for Tropical Plants", in: department)
  5. intro.nextCourse = intermediate
  6. intermediate.nextCourse = advanced
  7. department.courses = [intro, intermediate, advanced]

上面的代码创建了一个部门及其三门课程。入门课程和中级课程都在其nextCourse属性中存储了建议的下一门课程,该课程为学生完成此课程后应选择的课程提供了无用的可选参考。

一个无主的可选引用不会对其包装的类的实例保持强大的控制,因此也不会阻止ARC取消分配该实例。它的行为与ARC下无主引用相同,只是无主可选引用可以为nil

像非可选的无主引用一样,您有责任确保nextCourse始终引用尚未取消分配的课程。例如,在这种情况下,当您从中删除课程时,department.courses您还需要删除其他课程可能具有的对该课程的任何引用。

笔记

可选值的基础类型是Optional,这是Swift标准库中的一个枚举。但是,可选选项是不能用标记值类型的规则的例外unowned

包装类的可选控件不使用引用计数,因此您无需维护对可选控件的强引用。

无主引用和隐式展开的可选属性

上面关于弱引用和无主引用的示例涵盖了两个较常见的情况,其中有必要打破一个强引用周期。

PersonApartment实施例显示的情况下两个属性,这两者都允许是nil,有可能造成很强的参考周期。最好使用弱引用来解决此情况。

CustomerCreditCard示例显示的情况下这是允许的一个属性是nil和另一个属性,不能nil有引起强烈的参考周期的潜力。最好使用无主引用来解决此情况。

但是,存在第三种情况,在这两种情况下,两个属性都应始终具有值,并且nil初始化完成后,这两个属性都不应具有在这种情况下,将一个类的未拥有属性与另一类的隐式展开的可选属性组合起来很有用。

这样,初始化完成后就可以直接访问这两个属性(没有可选的展开),同时仍然避免了引用周期。本节说明如何建立这种关系。

下面的示例定义两个类CountryCity,每个类将另一个类的实例存储为属性。在此数据模型中,每个国家必须始终拥有一个首都,并且每个城市都必须始终属于一个国家。为了表示这一点,Country该类具有一个capitalCity属性,而City该类具有一个country属性:

  1. class Country {
  2. let name: String
  3. var capitalCity: City!
  4. init(name: String, capitalName: String) {
  5. self.name = name
  6. self.capitalCity = City(name: capitalName, country: self)
  7. }
  8. }
  9. class City {
  10. let name: String
  11. unowned let country: Country
  12. init(name: String, country: Country) {
  13. self.name = name
  14. self.country = country
  15. }
  16. }

要设置两个类之间的相互依赖关系,的初始化程序City需要一个Country实例,并将该实例存储在其country属性中。

从的初始化器City中调用的初始化器Country但是,两阶段初始化中所述,在将新实例完全初始化之前,的初始Country值设定项无法传递self初始值设定项CityCountry

为了满足此要求,您可以将的capitalCity属性声明Country为隐式未包装的可选属性,由其类型注释(City!末尾的感叹号指示这意味着该capitalCity属性具有默认值nil,就像其他任何可选属性一样,但是可以访问属性,而无需如Implicitly Unwrapped Optionals中所述解开其值

因为capitalCity具有默认nil值,所以Country一旦Country实例name在其初始值设定项中设置了其属性,便认为该新实例已完全初始化。这意味着Country初始化器可以selfname属性设置后立即开始引用并传递隐式属性Country因此selfCityCountry初始化程序设置自己的capitalCity属性时,可以初始化程序作为初始化程序的参数之一传递

所有这些意味着您可以在单个语句中创建CountryandCity实例,而无需创建一个强大的引用周期,并且capitalCity可以直接访问属性,而无需使用感叹号来包装其可选值:

  1. var country = Country(name: "Canada", capitalName: "Ottawa")
  2. print("\(country.name)'s capital city is called \(country.capitalCity.name)")
  3. // Prints "Canada's capital city is called Ottawa"

在上面的示例中,使用隐式解包的可选意味着所有两阶段类初始化器要求都得到满足。capitalCity一旦初始化完成,就可以像使用非可选值一样使用和访问属性,同时仍然避免了强引用周期。

封闭的强大参考周期

上面您看到了当两个类实例属性相互保持强引用时如何创建强引用循环。您还看到了如何使用弱引用和无主引用来打破这些强引用周期。

如果将闭包分配给类实例的属性,并且该闭包的主体捕获该实例,则也会发生强引用循环。之所以会发生这种捕获,是因为闭包的主体访问实例的属性,例如self.someProperty,或者因为闭包在实例上调用了一个方法,例如self.someMethod()无论哪种情况,这些访问都会导致闭包被“捕获” self,从而创建了一个强大的参考周期。

之所以会发生这种强烈的引用循环,是因为闭包(如类)是引用类型将闭包分配给属性时,就是在分配对该闭包引用从本质上讲,这是与上述相同的问题-两个强引用使彼此保持生命。但是,这次不是两个类实例,而是一个彼此保持生命的类实例和一个闭包。

Swift为这个问题提供了一种优雅的解决方案,称为闭包捕获列表但是,在学习如何使用闭包捕获列表打破强参考循环之前,了解如何导致这种循环很有用。

下面的示例显示了在使用引用封闭的方法时如何创建强引用循环self此示例定义了一个名为的类HTMLElement该类为HTML文档中的单个元素提供了一个简单的模型:

  1. class HTMLElement {
  2. let name: String
  3. let text: String?
  4. lazy var asHTML: () -> String = {
  5. if let text = self.text {
  6. return "<\(self.name)>\(text)</\(self.name)>"
  7. } else {
  8. return "<\(self.name) />"
  9. }
  10. }
  11. init(name: String, text: String? = nil) {
  12. self.name = name
  13. self.text = text
  14. }
  15. deinit {
  16. print("\(name) is being deinitialized")
  17. }
  18. }

HTMLElement类定义了一个name属性,该属性指示该元素的名称,诸如"h1"用于一个标题元素,"p"为段落元件,或"br"用于换行元件。HTMLElement还定义了一个可选text属性,您可以将其设置为表示要在该HTML元素内呈现的文本的字符串。

除了这两个简单属性外,HTMLElement该类还定义了一个名为的惰性属性asHTML此属性引用一个合并nametext合并为HTML字符串片段的闭包asHTML属性的类型为,或“一个不带参数并返回值的函数”。() -> StringString

默认情况下,为asHTML属性分配一个闭包,该闭包返回HTML标记的字符串表示形式。text如果存在,则此标签包含可选值;如果不存在,则不包含文本内容text对于段落元素,闭包将返回,具体取决于属性是否等于"<p>some text</p>""<p />"text"some text"nil

asHTML属性的命名和使用有点像实例方法。但是,由于


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Swift5.4语言指南(十)枚举发布时间:2022-07-13
下一篇:
OpenStack Swift集群与Keystone的整合使用说明发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap