了解java的童鞋都知道GC(垃圾回收),程序员不需要去关心内存动态分配和垃圾回收的问题,交友jvm去处理。在Swift中也有类似的机制,那它是依据什么样的策略来进行垃圾回收(释放空间)的呢?
ARC是如何进行工作的?###
*** 每当你创建一个class的instance,ARC会自动分配空间存储实例信息以及所有与其相关联的属性。一旦当这个实例步不再被使用,ARC就会释放这个实例持有的空间,这样就保证了一些永远不会被用到的实例一直占用内存导致内存out of memory。然而,ARC有时会释放正在使用的实例,这时如果你去方位这个实例的属性或者是方法,你的App可能就会crash掉。为了防止这种情况的发生,ARC会记录每个instance reference的个数,只要还存在一个有效的reference,ARC就不会释放该instance。距离说明如下:***
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") }
}
每个Person实例都有一个name属性和所居住的apartment,但此时apartment的类型是Optional的,表示可能有的人可能不住apartment或其他一些情况。
每个Apartment都有一个单元号unit,以及居住在其中的房客tenant,tenant也是Optional类型,表示可能改公寓还没有人在住。
创建两个实例,分别是:joh和unit4A,其初始值都为nil
var jhon:Person?
var unit4A:Apartment?
一旦我们开始为它们赋值,比如:
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
那么,jhon相对于Person就有了一个strong reference,同理unit4A对于Apartment也有了一个strong reference。我们以图的形式展示如下:
这时我们如果将john和unit4A全部赋值为nil,则会调用对应的deinit方法,ARC会回收两个变量的占用空间。但是,如果我们如下面这样去操作:
john!.apartment = unit4A
unit4A!.tenant = john
对应的关系则变为了:
此时再将john和unit4A赋值为nil,你会发现对应的deinit方法没有被调用,ARC并没有回收此空间。因为我们上面说过,只有有一个strong reference存在,ARC就不会回收。
出现传说中的内存泄漏。
Swift针对此种情况给出了相应的解决方法:两个实例相互持有,且都可以为nil,使用weak关键字避免出现cycleReference,Code如下:
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 }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person
var unit4A: Apartment
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
如上重新定义Person和Apartment,并创建实例john和unit4A,对应的关系:
这是在将jhon赋值为nil,ARC就或自动回收其持空间啦~而此时unit4A!.tenant并不为nil。因为还有一个weak reference存在,但weak reference不会阻止ARC回收其空间,当回收完毕unit4A!.tenant则自动被置为nil,正因为此上面定义Apartment的tenant属性是使用了var而不是let。
****类似的还有unowned,直白的翻译就是不拥有,还是很容易让人理解呀!与weak相比共同点就是都不保持strong hold(官方这么叫,一时间不知道咋翻译啦);不同点是当其他实例有相同或更长的生命周期,任何一个unowned reference都会被当做有值,ARC也不会将其置为nil,如此声明的时候就可以使用let。示例如下:***
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") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
这时,Customer有了一个strong reference指向CreditCard,同时CreditCard有unowned reference指向Customer:
而此时将john置为nil,由于unowned customer reference,发现两个deinit方法都会执行。
额外的,对于Closures也有类似的cycle reference需要特别注意,具体的避免方法同上,下面之举一个简单的例子:###
class HTMLElement {
let name: a href="" String /a
let text: a href="" String /a ?
lazy var asHTML: () -> a href="" String /a = {
if let text = self.text {
return "<\(self.name)>\(text)\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: a href="" String /a , text: a href="" String /a ? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var paragraph: a href="" HTMLElement /a ? = HTMLElement(name: "p", text: "hello, world")
其reference如图所示:
解决方法:
class HTMLElement {
let name: a href="" String /a
let text: a href="" String /a ?
lazy var asHTML: () -> a href="" String /a = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: a href="" String /a , text: a href="" String /a ? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}