Swift-ARC(Automatic Reference Counting)

了解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。我们以图的形式展示如下:

Swift-ARC(Automatic Reference Counting)_第1张图片
strongReference.jpeg

这时我们如果将john和unit4A全部赋值为nil,则会调用对应的deinit方法,ARC会回收两个变量的占用空间。但是,如果我们如下面这样去操作:

john!.apartment = unit4A
unit4A!.tenant = john

对应的关系则变为了:


Swift-ARC(Automatic Reference Counting)_第2张图片
cycleReference.jpeg

此时再将john和unit4A赋值为nil,你会发现对应的deinit方法没有被调用,ARC并没有回收此空间。因为我们上面说过,只有有一个strong reference存在,ARC就不会回收。


Swift-ARC(Automatic Reference Counting)_第3张图片
cycleReference.jpeg

出现传说中的内存泄漏。
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,对应的关系:

Swift-ARC(Automatic Reference Counting)_第4张图片
weakReference.jpeg

这是在将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:

Swift-ARC(Automatic Reference Counting)_第5张图片
weakReference.jpeg

而此时将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)"
        } 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如图所示:

Swift-ARC(Automatic Reference Counting)_第6张图片
addtional.jpeg

解决方法:

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)"
        } 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")
    }
    
}

你可能感兴趣的:(Swift-ARC(Automatic Reference Counting))