Swift4.2语言规范(二十五) 自动引用计数(ARC)
Swift使用自动引用计数(ARC: Automatic Reference Counting)来跟踪和管理应用程序的内存使用情况。在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理。当不再需要这些实例时,ARC会自动释放类实例使用的内存。 但是,在少数情况下,ARC需要有关代码部分之间关系的更多信息,以便为您管理内存。本章介绍了这些情况,并说明了如何启用ARC来管理所有应用程序的内存。在Swift中使用ARC非常类似于将ARC与Objective-C一起使用转换为ARC发行说明中所述的方法。 引用计数仅适用于类的实例。结构和枚举是值类型,而不是引用类型,并且不通过引用存储和传递。 ARC是如何工作的每次创建类的新实例时,ARC都会分配一块内存来存储有关该实例的信息。此内存保存有关实例类型的信息,以及与该实例关联的任何存储属性的值。 此外,当不再需要实例时,ARC释放该实例使用的内存,以便可以将内存用于其他目的。这可确保类实例在不再需要时不会占用内存空间。 但是,如果ARC要释放仍在使用的实例,则将无法再访问该实例的属性,或调用该实例的方法。实际上,如果您尝试访问该实例,您的应用很可能会崩溃。 为了确保实例在仍然需要时不会消失,ARC会跟踪当前引用每个类实例的属性,常量和变量的数量。只要至少有一个对该实例的活动引用仍然存在,ARC就不会释放实例。 为了实现这一点,无论何时将类实例分配给属性,常量或变量,该属性,常量或变量都会对实例进行强引用。该引用被称为“强”引用,因为它保持对该实例的坚定持有,并且只要该强引用仍然存在就不允许它被释放。 ARC在行动以下是自动引用计数如何工作的示例。此示例以一个名为的简单类开头,该类 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 } 本 下一个代码片段定义了三个类型的变量 1 var reference1: Person? 2 var reference2: Person? 3 var reference3: Person? 您现在可以创建一个新 1 reference1 = Person(name: "John Appleseed") 2 // Prints "John Appleseed is being initialized" 请注意,消息是在您调用类的初始化程序时打印的。这证实已经进行了初始化。 因为新 如果将同一 1 reference2 = reference1 2 reference3 = reference1 现在有三个对这个单一 如果通过分配 1 reference1 = nil 2 reference2 = nil ARC不会释放 1 reference3 = nil 2 // Prints "John Appleseed is being deinitialized" 类实例之间的强引用循环在上面的示例中,ARC能够跟踪 但是,可以编写一个代码,其中类的实例永远不会达到零强引用的程度。如果两个类实例彼此具有强引用,则会发生这种情况,这样每个实例都会使另一个实例保持活动状态。这被称为强引用周期。 您可以通过将类之间的某些关系定义为弱引用或无引用而不是强引用来解决强引用循环。在解决类实例之间的强引用循环中描述了此过程。但是,在您学习如何解决强引用循环之前,了解如何引起这样的循环是有用的。 这是一个如何通过意外创建强引用循环的示例。这个例子定义了两个叫做 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 8 class Apartment { 9 let unit: String 10 init(unit: String) { self.unit = unit } 11 var tenant: Person? 12 deinit { print("Apartment (unit) is being deinitialized") } 13 } 每个 类似地,每个 这两个类还定义了一个deinitializer,它打印出该类的一个实例被取消初始化的事实。这使您可以查看是否按预期释放实例 下一个代码片段定义了两个名为 1 var john: Person? 2 var unit4A: Apartment? 您现在可以创建特定的 1 john = Person(name: "John Appleseed") 2 unit4A = Apartment(unit: "4A") 以下是创建和分配这两个实例后强引用的外观。该 您现在可以将两个实例链接在一起,以便此人拥有公寓,并且公寓有租户。注意,感叹号( 1 john!.apartment = unit4A 2 unit4A!.tenant = john 以下是将两个实例链接在一起后强引用的外观: 不幸的是,链接这两个实例会在它们之间产生强大的引用周期。 1 john = nil 2 unit4A = nil 请注意,将这两个变量设置为时,都不会调用deinitializer? 以下是在将
解决类实例之间的强引用循环当您使用类类型的属性时,Swift提供了两种解决强引用循环的方法:弱引用和无引用引用。 弱引用和无引用引用使引用周期中的一个实例能够引用另一个实例而不保持强大的保持。然后,实例可以相互引用而不会创建强大的引用周期。 当另一个实例的生命周期更短时,即在可以首先释放另一个实例时,使用弱引用。在 弱引用一个弱引用是不保留对实例的强抱它指的是,所以不从引用的实例处置停止ARC的引用。此行为可防止引用成为强引用循环的一部分。通过 因为弱引用不会对它引用的实例保持强大的保持,所以在弱引用仍然引用它的情况下可以释放该实例。因此,ARC会自动设置一个弱引用,以 您可以检查弱引用中是否存在值,就像任何其他可选值一样,并且您永远不会最终得到对不再存在的无效实例的引用。 注意 当ARC设置弱引用时,不会调用属性观察者 下面的例子是相同的 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 8 class Apartment { 9 let unit: String 10 init(unit: String) { self.unit = unit } 11 weak var tenant: Person? 12 deinit { print("Apartment (unit) is being deinitialized") } 13 } 来自两个变量( 1 var john: Person? 2 var unit4A: Apartment? 3 4 john = Person(name: "John Appleseed") 5 unit4A = Apartment(unit: "4A") 6 7 john!.apartment = unit4A 8 unit4A!.tenant = john 现在,您将这两个实例链接在一起的引用文件如下: 该 1 john = nil 2 // Prints "John Appleseed is being deinitialized" 因为没有对 对该 1 unit4A = nil 2 // Prints "Apartment 4A is being deinitialized" 因为没有对该 注意 在使用垃圾收集的系统中,弱指针有时用于实现简单的缓存机制,因为只有当内存压力触发垃圾收集时才会释放没有强引用的对象。但是,使用ARC时,一旦删除了最后一个强引用,就会释放值,使得弱引用不适用于此类目的。 无主引用就像一个弱引用一样,无主引用并不会对它所引用的实例保持强势。但是,与弱引用不同,当另一个实例具有相同的生命周期或更长的生命周期时,将使用无主引用。通过 预期无主引用值始终具有值。因此,ARC永远不会将无主引用的值设置为 重要 仅当您确定引用始终引用尚未释放的实例时,才使用无主引用。 如果在取消分配该实例后尝试访问无主引用的值,则会出现运行时错误。 下面的示例定义两个类, 之间的关系 此外,只能通过将值和实例传递给自定义初始值设定项来创建新 由于信用卡将始终拥有客户,因此您将其 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 10 class CreditCard { 11 let number: UInt64 12 unowned let customer: Customer 13 init(number: UInt64,customer: Customer) { 14 self.number = number 15 self.customer = customer 16 } 17 deinit { print("Card #(number) is being deinitialized") } 18 } 注意 该类的 下一个代码片段定义了一个 1 var john: Customer? 您现在可以创建一个 1 john = Customer(name: "John Appleseed") 2 john!.card = CreditCard(number: 1234_5678_9012_3456,customer: john!) 现在您已经链接了两个实例,这是引用的外观: 该 由于无主 因为没有更多对该 1 john = nil 2 // Prints "John Appleseed is being deinitialized" 3 // Prints "Card #1234567890123456 is being deinitialized" 上面的最后一个代码片段显示 注意 上面的示例显示了如何使用安全的无主引用。对于需要禁用运行时安全检查的情况,Swift还提供不安全的无主引用 - 例如,出于性能原因。与所有不安全的操作一样,您负责检查该代码的安全性。 您通过书面表示不安全的无主引用 无主引用和隐式解包的可选属性上面的弱和无主引用的例子涵盖了两个更常见的场景,其中必须打破强大的引用周期。 在 在 但是,还有第三种情况,其中两个属性应始终具有值,并且 这使得一旦初始化完成就可以直接访问这两个属性(没有可选的解包),同时仍然避免了引用周期。本节介绍如何设置此类关系。 下面的示例定义了两个类, 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 10 class City { 11 let name: String 12 unowned let country: Country 13 init(name: String,country: Country) { 14 self.name = name 15 self.country = country 16 } 17 } 要设置两个类之间的相互依赖性,初始化程序 初始化器 要处理此要求,请将 因为 所有这些意味着您可以在单个语句中创建 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" ? 在上面的示例中,使用隐式展开的可选项意味着满足所有两阶段类初始化程序要求。 闭包的强引用周期您在上面看到了当两个类实例属性相互之间具有强引用时,如何创建强引用循环。您还了解了如何使用弱和无主引用来打破这些强大的引用周期。 如果将闭包分配给类实例的属性,并且该闭包的主体捕获实例,则也会发生强引用循环。这种捕获可能是因为闭包的主体访问实例的属性,例如 这种强引用循环的发生是因为闭包(如类)是引用类型。为属性分配闭包时,您将分配对该闭包的引用。实质上,它与上面的问题相同 - 两个强引用相互保持活着。但是,这次是一个类实例和一个闭包,而不是两个类实例。 Swift为这个问题提供了一个优雅的解决方案,称为闭包捕获列表。但是,在学习如何使用闭包捕获列表打破强引用循环之前,了解如何引起这样的循环是有用的。 下面的示例显示了在使用引用的闭包时如何创建强引用循环 1 class HTMLElement { 2 3 let name: String 4 let text: String? 5 6 lazy var asHTML: () -> String = { 7 if let text = self.text { 8 return "<(self.name)>(text)</(self.name)>" 9 } else { 10 return "<(self.name) />" 11 } 12 } 13 14 init(name: String,text: String? = nil) { 15 self.name = name 16 self.text = text 17 } 18 19 deinit { 20 print("(name) is being deinitialized") 21 } 22 23 } 的 除了这两个简单属性之外, 默认情况下,为 该 例如, 1 let heading = HTMLElement(name: "h1") 2 let defaultText = "some default text" 3 heading.asHTML = { 4 return "<(heading.name)>(heading.text ?? defaultText)</(heading.name)>" 5 } 6 print(heading.asHTML()) 7 // Prints "<h1>some default text</h1>" 注意 该 的 以下是使用 1 var paragraph: HTMLElement? = HTMLElement(name: "p",text: "hello,world") 2 print(paragraph!.asHTML()) 3 // Prints "<p>hello,world</p>" 注意
不幸的是, 实例的 注意 即使闭包 如果将 paragraph = nil 请注意, 解决闭包的强引用周期通过将捕获列表定义为闭包定义的一部分,可以解决闭包和类实例之间的强引用循环。捕获列表定义在闭包体内捕获一个或多个引用类型时要使用的规则。与两个类实例之间的强引用循环一样,您将每个捕获的引用声明为弱引用或无主引用,而不是强引用。弱或无主的适当选择取决于代码的不同部分之间的关??系。 注意 每当你引用一个闭包中的成员时,Swift都要求你写 定义捕获列表捕获列表中的每个项目都是 将捕获列表放在闭包的参数列表之前,如果提供了它们,则返回类型: 1 lazy var someClosure: (Int,String) -> String = { 2 [unowned self,weak delegate = self.delegate!] (index: Int,stringToProcess: String) -> String in 3 // closure body goes here 4 } 如果闭包没有指定参数列表或返回类型,因为它们可以从上下文中推断出来,请将捕获列表放在闭包的最开头,然后是 1 lazy var someClosure: () -> String = { 2 [unowned self,weak delegate = self.delegate!] in 3 // closure body goes here 4 } 弱和无主引用当闭包和它捕获的实例将始终相互引用时,将闭包中的捕获定义为无主引用,并且将始终同时取消分配。 相反,当捕获的引用可能 注意 如果捕获的引用永远不会变为 无主引用是用于解决上面的闭包的强引用周期的 1 class HTMLElement { 2 3 let name: String 4 let text: String? 5 6 lazy var asHTML: () -> String = { 7 [unowned self] in 8 if let text = self.text { 9 return "<(self.name)>(text)</(self.name)>" 10 } else { 11 return "<(self.name) />" 12 } 13 } 14 15 init(name: String,text: String? = nil) { 16 self.name = name 17 self.text = text 18 } 19 20 deinit { 21 print("(name) is being deinitialized") 22 } 23 24 }
您可以 1 var paragraph: HTMLElement? = HTMLElement(name: "p",world</p>" 以下是引用文件在捕获列表中的显示方式: 这一次, 1 paragraph = nil 2 // Prints "p is being deinitialized" 有关捕获列表的详细信息,请参阅捕获列表。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |