Swift学习笔记-自动引用计数

参考书籍:
[1]管蕾.张玲玲.朱元波.Swift开发实战[M]. 北京:人民邮电出版社.2014.10-1 ISBN 978-7-115-36827-0
[2]陈隽.刘媛媛.Swift入门很简单[M]. 北京:清华大学出版社.2015.01-01 ISBN 978-7-302-38880-7

自动引用计数ARC是一种内存管理机制,用来跟踪、管理开发者应用程序所使用的内存。

一、工作机制

开发者每一次创建一个类实例时,ARC会分配一块内存用来管理存储实例相关的信息,比如实例的类型信息、与实例相关的属性值。当实例不再被使用时,ARC将会释放此实例所占的内存。这样保证不会使用的内存不会一直占用内存空间。

示例代码:

import Foundation

class Person{
    let name:String
    init(name: String){
        self.name = name
        print("\(name) start to init.")
        
    }
    deinit {
        print("\(name) was freed ")
    }
}
var referencel: Person? = Person(name: "John")//创建实例时,ARC将会分配内存
referencel = nil//设置为nil是,ARC释放内存

John start to init.
John was freed
Program ended with exit code: 0

修改版1,访问被释放的实例属性:

import Foundation

class Person{
    let name:String
    init(name: String){
        self.name = name
        print("\(name) start to init.")
        
    }
    deinit {
        print("\(name) was freed ")
    }
}
var referencel: Person? = Person(name: "John")
referencel = nil
print(referencel?.name)

终端如下:

John start to init.
John was freed
nil
Program ended with exit code: 0

解释:访问已经被释放的实例或方法,将会得到nil

修改版2,强制访问被释放的实例属性:

import Foundation

class Person{
    let name:String
    init(name: String){
        self.name = name
        print("\(name) start to init.")
        
    }
    deinit {
        print("\(name) was freed ")
    }
}
var referencel: Person? = Person(name: "John")
referencel = nil
print(referencel!.name)

John start to init.
John was freed
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)

即,强制访问会导致程序崩溃。

二、循环强引用

开发者可能会写出“在程序中使用多个强引用”,而这些强引用不知不觉形成循环强引用,而导致内存泄露。

1、类实例之间循环强引用

多个类互相保留对方的强引用,并让对方不被销毁。例如:

import Foundation

class Person{
    let name:String
    init(name: String){
        self.name = name
        print("\(name) start to init.")
        
    }
    var apartment: Apartment? //属性声明
    deinit {
        print("\(name) was freed ")
    }
}
class Apartment{
    let number: Int
    init(number: Int){
        self.number = number
    }
    var tenant: Person?//属性声明
    deinit{
        print("Apartment #\(number) was freed")
    }
}
var referencel: Person? = Person(name: "John")
var number: Apartment? = Apartment(number: 74)
print(referencel?.name)
print(number?.number)
referencel!.apartment=number//为对象中的属性赋值
number!.tenant = referencel//为对象中的属性赋值
referencel = nil
number = nil

终端如下:

John start to init.
Optional(“John”)
Optional(74)
Program ended with exit code: 0

上例,两个实例创建循环强引用,导致引用计数并不会将为0,实例未被ARC销毁。

2、闭包引起的循环强引用

即,开发者将一个闭包赋值给类实例的某个属性时,在这个闭包体中又使用了实例,或者闭包体中可能访问了实例的某个属性、方法,导致产生循环强引用。

例如:

import Foundation

class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML:() -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        }else {
            return "<\(self.name)/>"
        }
    }
    init (name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    deinit {
        print("\(name) was freed")
    }
}
var paragraph: HTMLElement? = HTMLElement(name: "p" , text: "Hello,world")
print(paragraph!.asHTML())
paragraph = nil

终端:

<p>Hello,world</p>
Program ended with exit code: 0

上面例子中,闭包实现的功能是赋值给asHTML属性,返回一个代表HTML标签的字符串。

使用HTMLElement创建的实例和asHTML默认值的闭包之间的循环强引用,使得paragraph设置为nil时,ARC没有释放实例。

HTMLElement类的实例name:”p”   – strong ->   ()->String中self.name

HTMLElement类的实例text:”hello world”<- strong –    ()->String中self.text

三、循环强引用解决方法

1、解决实例之间的循环强引用

1.1 弱引用

弱引用不会牢牢保留住引用实例,不会阻止ARC销毁被引用的实例,这种行为阻止了引用变为循环强引用。

例如:

import Foundation

class Person{
    let name:String
    init(name: String){
        self.name = name
        print("\(name) start to init.")
        
    }
    var apartment: Apartment?
    deinit {
        print("\(name) was freed ")
    }
}
class Apartment{
    let number: Int
    init(number: Int){
        self.number = number
    }
    weak var tenant: Person?//使用weak关键字(必须为可变存储属性)
    deinit{
        print("Apartment #\(number) was freed")
    }
}
var referencel: Person? = Person(name: "John")
var number: Apartment? = Apartment(number: 74)
print(referencel?.name)
print(number?.number)
referencel!.apartment=number
number!.tenant = referencel
referencel = nil
number = nil

终端如下(成功释放):

John start to init.
Optional(“John”)
Optional(74)
John was freed
Apartment #74 was freed
Program ended with exit code: 0

1.2 无主引用

无主引用也不会保留引用的实例,和弱引用不同的是,无主引用永远有值。因此无主引用总是被定义为非可选类型。

开发者声明属性或者变量/常量是,在前面加上关键字unowned表示这是一个无主引用,语法形式:

unowned let/var 属性/常量/变量:实例

 

1.3 无主引用和隐式解析可选属性

2、解决闭包所引起的循环强引用

闭包所引起的循环强引用,可以通过定义闭包的同时定义捕获列表作为闭包的一部分解决。捕获列表定义了闭包内捕获一个或者多个引用类型的规则。跟解决类实例键的循环强引用一样,需要声明每个捕获的引用为弱引用或无主引用。

2.1定义捕获列表

捕获列表中每个元素都是由weak 或unowned关键字和实例引用(如self或someInstance)成对组成。每一对都是方括号内,通过逗号分开,定义形式如下:

[关键字 self]

2.2若引用和无主引用

当闭包和捕获的实例总是相互作用,并且总是同时销毁时,将闭包内的捕获定义为无主引用;相反,当捕获引用有时可能为nil时,将闭包内捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用值会自动置为nil。这可以让开发者在闭包内检查他们是否存在。

例如:使用捕获列表解决循环强引用

import Foundation

class HTMLElement {
    let name: String
    let text: String?
    lazy var asHTML:() -> String = {
        [unowned self] in //这里
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        }else {
            return "<\(self.name)/>"
        }
    }
    init (name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    deinit {
        print("\(name) was freed")
    }
}
var paragraph: HTMLElement? = HTMLElement(name: "p" , text: "Hello,world")
print(paragraph!.asHTML())
paragraph = nil

终端如下(成功释放):

<p>Hello,world</p>
p was freed
Program ended with exit code: 0