
在早期的 iOS 开发中,内存管理是由开发者手动来完成的。因为传统的垃圾回收机制对于移动平台来说十分低效,苹果采用的是引用计数(RC,Reference Counting)的方式来管理内存,开发者需要通过手工的方式增加或减少一个实例的引用计数。在 iOS 5 之后,引入了 ARC 自动引用计数,使得开发者不需要手动地调用 @H_404_8@retain@H_404_8@ 和 @H_404_8@release@H_404_8@ 来管理引用计数,但是实际上这些方法还是会被调用,只不过是交给了编译器来完成,编译器会在合适的地方帮我们加入这些方法。@H_404_8@
@H_404_8@ 什么是自动引用计数?@H_404_8@@H_404_8@
@H_404_8@
每当你创建一个类的实例的时候,ARC 便会自动分配一块内存空间来存放这个实例的信息,当这个实例不再被使用的时候,ARC 便释放实例所占用的内存。一般每个被管理的实例都会与一个引用计数器相连,这个计数器保存着当前实例被引用的次数,一旦创建一个新的引用指向这个实例,引用计数器便加 1,每当指向该实例的引用失效,引用计数器便减 1,当某个实例的引用计数器变成 0 的时候,这个实例就会被立即销毁。@H_404_8@
在 Swift 中,对引用描述的关键字有三个:@H_404_8@strong@H_404_8@,@H_404_8@weak@H_404_8@uNowned@H_404_8@,所有的引用没有特殊说明都是 @H_404_8@ 强引用类型。在 ARC 中,只有指向一个实例的所有 @H_404_8@ 强引用都断开了,这个实例才会被销毁。@H_404_8@
举一个简单的例子:@H_404_8@
class A { let name: String init(name: String) { self.name = name } deinit { print("A deinit") }}var a1: A?var a2: A?a1 = A(name: "A")a2 = a1a1 = nil@H_404_8@ 上面这个例子中,虽然 @H_404_8@a1@H_404_8@ 这个 @H_404_8@ 强引用断开了,但是还有 @H_404_8@a2@H_404_8@ 这个强引用指向这个实例,所以不会在命令行中输出 @H_404_8@A deinit@H_404_8@,当我们把 @H_404_8@ 也设置为 @H_404_8@nil@H_404_8@ 时,与这个实例关联的所有强引用均断开了,这个实例便会被销毁,在命令行中打印 @H_404_8@。@H_404_8@
但是,在某些情况下,一个类实例的强引用数永远不能变为 0,例如两个类实例互相持有对方的强引用,因而每个类实例都让对方一直存在,这就是所谓的强引用循环(Strong Reference Cycles)。@H_404_8@
这里引用 TSPL 中的例子:@H_404_8@
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") }}class Apartment { let unit: String init(unit: String) { self.unit = unit } var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") }}@H_404_8@ 每一个 @H_404_8@Person@H_404_8@ 实例有一个可选的初始化为 @H_404_8@ 的 @H_404_8@Apartment@H_404_8@ 类型,因为一个人并不总是拥有公寓。同样,每一个 @H_404_8@ 实例都有一个可选的初始化为 @H_404_8@ 类型,因为一个公寓并不总是属于一个人。@H_404_8@
接下来的代码片段定义了两个可选类型的变量 @H_404_8@john@H_404_8@unit4A@H_404_8@,并分别设定为下面的 @H_404_8@ 的实例,这两个变量都备受设定为 @H_404_8@:@H_404_8@
var john: Person?var unit4A: Apartment?@H_404_8@ 现在可以创建特定的 @H_404_8@ 实例,并将它们赋值给 @H_404_8@ 变量:@H_404_8@
john = Person(name: "John Appleseed")unit4A = Apartment(unit: "4A")@H_404_8@ 下面一段代码将这两个实例关联起来:@H_404_8@
john!.apartment = unit4Aunit4A!.tenant = john@H_404_8@ 将两个实例关联在一起后,强引用的关系如图所示:@H_404_8@
这两个实例关联之后,会产生一个循环强引用,当断开 @H_404_8@ 所持有的强引用时,引用计数器并不会归零,所以这两块空间也得不到释放,这就导致了内存泄漏。@H_404_8@
可以将其中一个类中的变量设定为 @H_404_8@ 弱引用来打破这种强引用循环:@H_404_8@
class Apartment { let unit: String init(unit: String) { self.unit = unit } weak var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") }}@H_404_8@ 当断开 @H_404_8@ 所持有的强引用时,@H_404_8@Person instance@H_404_8@ 的引用计数器变成 0,实例被销毁,从而 @H_404_8@Apartment instance@H_404_8@ 的引用计数器也变为 0,实例被销毁。@H_404_8@
@H_404_8@ 什么时候使用 @H_404_8@
weak@H_404_8@?@H_404_8@ 当两个实例是 optional 关联在一起时,确保其中的一个使用 @H_404_8@ 弱引用,就像上面所说的那个例子一样。@H_404_8@
@H_404_8@
uNowned 无主引用@H_404_8@
在某些情况下,声明的变量总是有值得时候,我们需要使用 @H_404_8@ 无主引用。@H_404_8@
同样借用一下 TSPL 中的例子:@H_404_8@
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") }}@H_404_8@ 这里定义了两个类,@H_404_8@Customer@H_404_8@CreditCard@H_404_8@,模拟了银行客户和客户的xyk,在这个例子中,每一个类都是将另一个类的实例作为自身的属性,所以会产生循环强引用。@H_404_8@
和之前那个例子不同的是,@H_404_8@ 类中有一个非可选类型的 @H_404_8@customer@H_404_8@ 属性,因为,一个客户可能有或者没有一张xyk,但是一张xyk总是关联着一个用户。@H_404_8@
var john: Customer?john = Customer(name: "John Appleseed")john!.card = CreditCard(number: 1234_5678_9012_3456,customer: john!)@H_404_8@ 关联两个实例后,它们的引用关系如图所示:@H_404_8@
变量持有的强引用时,再也没有指向 @H_404_8@ 的强引用了,所以该实例被销毁了,其后,再也没有指向 @H_404_8@ 的强引用了,该实例也被销毁了。@H_404_8@
@H_404_8@ 什么时候使用 @H_404_8@@H_404_8@
uNowned@H_404_8@ 无主引用?@H_404_8@@H_404_8@ 两个实例 A 和 B,如果实例 A 必须在实例 B 存在的前提下才能存在,那么实例 A 必须用 @H_404_8@ 无主引用指向实例 B。也就是说,有强制依赖性的那个实例必须对另一个实例持有无主引用。@H_404_8@
例如上面那个例子所说,银行客户可能没有xyk,但是每张xyk总是绑定着一个银行客户,所以xyk这个类就需要用 @H_404_8@ 无主引用。@H_404_8@
还有一种情况,两个属性都必须有值,并且初始化完成之后永远不会为 @H_404_8@。在这种情况下,需要一个类使用 @H_404_8@ 无主引用,另一个类使用隐式解析可选属性。@H_404_8@
@H_404_8@
在 Swift 中,闭包和函数都属于引用类型。并且闭包还有一个特性:可以在其定义的上下文中捕获常量或者变量。所以,在一个类中,闭包被赋值给了一个属性,而这个闭包又使用了这个类的实例的时候,就会引起循环强引用。@H_404_8@
Swift 提供了一种方法来解决这个问题:闭包捕获列表(closure capture List)。在定义闭包的同时定义捕获列表作为闭包的一部分,捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例之间的循环强引用一样,声明每个捕获的引用为弱引用或者无主引用。@H_404_8@
捕获列表中的每一项都由一对元素组成,一个元素是 @H_404_8@ 或者 @H_404_8@ 关键字,另一个元素是类实例的引用(例如最常见得是 @H_404_8@self@H_404_8@),这些在方括号内用逗号隔开。@H_404_8@
weak,何时使用 uNowned@H_404_8@
@H_404_8@
在闭包和捕获的实例总是相互引用并且总是同时销毁的时候,将闭包内的捕获定义为 @H_404_8@在被捕获的实例可能变成 @H_404_8@ 的情况下,使用 @H_404_8@ 弱引用。如果被捕获的引用绝对不会变成 @H_404_8@,应该使用 @H_404_8@ 无主引用,而不是 @H_404_8@ 弱引用。@H_404_8@
@H_404_8@
其实 ARC 应该也算 GC 的一种,不过我们一谈到 GC,大多都会想到 Java 中的垃圾回收机制,相比较 GC,ARC 简单得许多。以后有机会可以讨论一下 Java 中的内存管理。@H_404_8@
另外,需要注意的一点是,这里所讲的都是针对于@H_404_8@引用类型@H_404_8@结构体@H_404_8@和@H_404_8@枚举@H_404_8@在 Swift 中属于值类型,不在 ARC 的考虑范围之内。@H_404_8@
@H_404_8@
尚学堂:每天推送IT新技术文章,跟着我们扩展技术视野吧@H_404_8@@H_404_8@
即刻起关注尚学堂,发送“要课程”,@H_404_8@
IT课程免费送!@H_404_8@
@H_404_8@
以上是内存溢出为你收集整理的Swift 中的内存管理详解全部内容,希望文章能够帮你解决Swift 中的内存管理详解所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)