聊一聊,Swift的内存管理
在Objective-C中无论ARC、MRC,开发中我们还是需要对内存进行管理,主要在于,避免循环引用、代理、KVO移除、Block这些,再有就是属性声明时的语义(property_attribute)。关于Objective-C中的引用计数制我就不重复了。
可到了Swift中,对于属性的声明就只剩下了lazy\weak。这个可让这些从Objective-C走来的程序员们一下就犯了难。
其实并非如此,Swift中依然需要所有权声明,请大家一定要认真阅读下边的内容。
聊一聊,Swift的内存管理
1、自动引用计数的工作机制
当你每次创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。内存中会包含实例的类型信息,以及这个实例所有相关属性的值。
此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
2、举个栗子
接下来的代码片段定义了三个类型为Person?的变量,用来按照代码片段中的顺序,为新的Person实例建立多个引用。由于这些变量是被定义为可选类型(Person?,而不是Person),它们的值会被自动初始化为nil,目前还不会引用到Person类的实例。
var reference1: Person?
var reference2: Person?
var reference3: Person?
现在你可以创建Person类的新实例,并且将它赋值给三个变量中的一个:
reference1 = Person(name: "John Appleseed")
// prints "John Appleseed is being initialized”
应当注意到当你调用Person类的构造函数的时候,“John Appleseed is being initialized”会被打印出来。由此可以确定构造函数被执行。
由于Person类的新实例被赋值给了reference1变量,所以reference1到Person类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证Person实例被保持在内存中不被销毁。
如果你将同一个Person实例也赋值给其他两个变量,该实例又会多出两个强引用:
reference2 = reference1
reference3 = reference1
现在这一个Person实例已经有三个强引用了。
如果你通过给其中两个变量赋值nil的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,Person实例不会被销毁:
reference1 = nil
reference2 = nil
在你清楚地表明不再使用这个Person实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:
reference3 = nil
// 打印 “John Appleseed is being deinitialized”
3、类实例之间的循环强引用
我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
你可以通过定义类之间的关系为weak弱引用或unowned无主引用,以替代强引用,从而解决循环强引用的问题。
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64,customer: Customer) {
self.number = number
self.customer = customer
}
"Card #\number) is being deinitialized") }
}
4、创建一个自动释放池(Autorelease Pool)
在 Objective – C 框架链接的应用程序,通常在它们的每一个线程必须创建至少 一个自动释放池。如果应用程序使用管理模型,即应用程序处理的 retain 和 release 对象,那么自动释放池捕获任何从该线程 autorelease 的对象。在Swift框架下呢??最近在Spritekit下做了一个Swift的demo。然后为了加载时高效一点,在子线程中加载背景,结果发现内存永远的增涨,以0.2MB,0.2MB的固定速度增涨,
排查最后,发现刚好背景图片切片的大小是0.2MB。这样的话,想来是没有别的原因了。我已经按照,正常的逻辑,在画面离开屏幕时就删除节点了,但是内存仍然在增加。想来也是没有其他原因。 在Swift中,子线程的内存仍然需要添加自动释放池,结果Swift开发指南中,并没有提到。
不过还是在Swifter中找到了,我想要的。
在 Swift 中我们也是能使用 autoreleasepool 的 — 虽然语法上略有不同。相比于原来在 Objective-C 中的关键字,现在它变成了一个接受闭包的方法:
func autoreleasepool(code: () -> ())
利用尾随闭包的写法,很容易就能在 Swift 中加入一个类似的自动释放池了:
func loadBigData() {
if let path = NSBundle.mainBundle()
.pathForResource("big",ofType: "jpg") {
for i in 1...10000 {
autoreleasepool {
let data = NSData.dataWithContentsOfFile(
path,options: nil,error: nil)
NSThread.sleepForTimeInterval(0.5)
}
}
}
}
这样改动以后。果然程序变得舒服多了。
经过ARC和Swift的双重隐藏,内存管理已经越来越不显眼了,但是其中的坑依然不少,希望大家通过本文,能加深对Swift的理解。