自动引用计数、循环引用(这个必须理解,必须看)

注:本文详细介绍自动引用计数,以及各种循环引用问题。一网打尽!


1、 自动引用计数原理

Swift 使用ARC机制来跟踪和管理你的内存,一般情况下,Swift 的内存管理机制会一直起着作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。

然而,在少数情况下,ARC 为了能帮助你管理内存,需要更多的关于你的代码之间关系的信息。本章描述了这些情况,并且为你示范怎样启用 ARC 来管理你的应用程序的内存。

为了确保在使用中的实例不会被销毁 , ARC会跟踪和计算每一个实例正在被多少属性、常量或者变量所使用,直到引用计数为 0 ,ARC就会销毁这个实例

所以,无论你将实例赋值给属性、变量或者常量的时候,他们都回创建此实例的强引用 ,会将实例保持住(引用计数+1) 只要强引用还在,实例是不允许销毁的 。 
下面来看一个例子就能轻松理解啦!

class Student{
var name:String
init(name:String){
self.name = name;
print("\(name) 实例被创建了 ")
}
deinit{
print("\(self.name) 实例被销毁啦!!!")
}
}
var st1:Student?
var st2:Student?
var st3:Student?
//这里是一个强引用
st1 = Student(name:"张三") //张三 实例被创建了
st2 = st1
st3 = st1
//现在Student有三个强引用了
st1=nil;
st2=nil
//将这两个释放 ,这时候还有一个强引用
st3 = nil //张三 实例被销毁啦!!!
//这时候所有的强引用都释放 , 则释放这个实例
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

这里注释写的很清楚 , 只有当所有的强引用释放掉 ,ARC才会释放这个实例

我们有时候会写出循环强引用的代码 。 如果两个实例互相持有对方的强引用 ,所以每个实例都会让对方一直存在着,一直不会被销毁 ,这就是所谓的循环强引用 。

2 、类实例之间的循环强引用

这时候你可以定义类之间的关系为弱引用或者无主引用,以替代强引用。从而解决循环强引用的问题 。

下面我们先来了解一下他是怎么产生的 :


class Animal {
var name:String
var food:Food?
init(name:String){
self.name = name
}
deinit{
print("Animal 被销毁啦")
}
}
class Food {
var name:String
var animal:Animal?
init(name:String){
self.name = name
}
deinit{
print("Food 被销毁啦 ")
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

这里在Animal类中引用了Food 在Food中引用了Animal ,都是强引用

var dog:Animal? = Animal(name: "dog")
var dogFood:Food? = Food(name: "meat")
dog!.food = dogFood
dogFood!.animal=dog
dog = nil
dogFood = nil
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然而并不会调用析构函数 ,循环引用会一直阻止这两个实例被销毁 应用程序造成了内存泄露

  • 解决循环强引用问题

Swift提供了两种办法来解决这个问题 弱引用 和 无主引用 对于生命周期中会变成nil的实例 使用弱引用 ,对于初始化赋值后。再也不会被赋值为nil的实例使用无主引用。

a 、弱引用

class Animal1 {
var name:String
var food:Food1?
init(name:String){
self.name = name
}
deinit{
print("Animal1 被销毁啦")
}
}
class Food1 {
var name:String
weak var animal:Animal1?
init(name:String){
self.name = name
}
deinit{
print("Food1 被销毁啦 ")
}
}
var dog1:Animal1? = Animal1(name: "dog")
var dogFood1:Food1? = Food1(name: "meat")
dog1!.food = dogFood1
dogFood1!.animal=dog1
dogFood1 = nil
dog1 = nil
//Animal1 被销毁啦
//Food1 被销毁啦
//哈哈 两个都可以被销毁啦
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

b 、无主引用 
无主引用也不会牢牢保持住引用的实例 , 和弱引用不同 ,无主引用永远是有值得 。无主引用总是被定义位非可选类型 。你可以在声明的时候在前面加上关键字 unowend 来表示这是个无主引用  
ARC无法在实例销毁的时候将它设置位nil 因为非可选的变量永远不能被赋值为nil ,但是在实例销毁后也是无法用这个实例去访问无主引用的, 如果你强制访问 会报运行时错误

class Customer {
var name:String
var card:Card?
init(name:String){
self.name = name
}
deinit{
print("Customer 被销毁")
}
}
class Card {
var name:String
unowned var customer:Customer //一个卡肯定要对应一个顾客
init(name:String,cus:Customer){
self.name = name
self.customer = cus
}
deinit{
print("Card 被销毁")
}
}
var customer:Customer? = Customer(name: "张三")
var card:Card? = Card(name: "招商银行金卡", cus: customer!)
customer!.card = card
customer = nil
card = nil
//Customer 被销毁
//Card 被销毁
//card!.customer //fatal error: unexpectedly found nil while unwrapping an Optional value
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

这个也被顺利的销毁 ,当销毁后再取访问的时候就不能访问了

前面两种解决循环引用的场景 ,第一种是两个都允许为nil , 第二个是有一个不允许为nil,但是如果两个属性都不允许位nil ,初始化完成后永远不能为nil ,这时候就需要一个类使用无主 , 另一个使用隐式解析可选属性  
c、隐式解析可选属性

class Country{
var name:String
var city:City!
init(name:String,cityName:String){
self.name = name
self.city = City(name: cityName, country: self)
}
deinit{
print("Country 被销毁啦 ")
}
}
class City{
var name:String
unowned var country:Country
init(name:String,country:Country){
self.name = name
self.country=country
}
deinit{
print("City 被销毁啦")
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

var country:Country? = Country(name: "China", cityName: "ShangHai")
//这一条语句创建了两个实例
var city = country!.city
print(city.country.name) //China
city = nil
country!.city = nil //City 被销毁啦
country = nil //Country 被销毁啦
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里注释都写的很清楚,代码很简单 ,就不再赘述

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

循环强引用还会发生在你将一个闭包赋值给一个类的属性 , 闭包中又使用了这个类的实例 ,其实 这跟之前的是一样的 ,因为闭包也是引用类型的 , 你把一个引用类型赋值给一个属性 ,然后在这个引用类型中使用这个实例 ,两个强引用一直有效. 
使用闭包捕获列表可以解决这个问题

下面看看 这个问题是怎么产生的 .


class Book{
var name:String
//这里定义为lazy才可以使用self 。在构造器第一阶段还不能使用self
lazy var oldName:Void -> String = {
return "old\(self.name)"
}
init(name:String){
self.name = name
}
deinit{
print("Book 被销毁了")
}
}
var book:Book? = Book(name: "Swift书")
print(book!.oldName()) //oldSwift书
//oldName是一个闭包
book = nil
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这时候并没有打印出析构方法的句子。明显产生了循环引用

解决办法 : 
定义捕获列表:捕获列表中的每一项都由一对元素组成 ,一个是weak 或者 unowned ,另一个是类的实例或者初始化变量


class Book1{
var name:String
//这里定义为lazy才可以使用self 。在构造器第一阶段还不能使用self
lazy var oldName:Void -> String = {
[unowned self] in
return "old\(self.name)"
}
init(name:String){
self.name = name
}
deinit{
print("Book 被销毁了")
}
}
var book1:Book1? = Book1(name: "Swift书")
print(book1!.oldName()) //oldSwift书
//oldName是一个闭包
book1 = nil //Book 被销毁了
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

这时会就被顺利销毁了

看下语法 :

@lazy var someClosure: (Int, String) -> String = {
[unowned self] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
@lazy var someClosure: () -> String = {
[unowned self] in
// closure body goes here
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

按照上述两种语法就可以了。

当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。

相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包内检查它们是否存在

@import url(http://i.cnblogs.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);

swift详解之九---------------自动引用计数、循环引用的更多相关文章

  1. 【iOS】自动引用计数 (循环引用)

    历史版本 ARC(Automatic Reference Counting,自动引用计数)极大地减少了Cocoa开发中的常见编程错误:retain跟release不匹配.ARC并不会消除对retain ...

  2. CCCallFuncN误用导致引用计数循环引用

    昨天测试“角色被遮挡部分透明显示”功能时,发现角色死亡后,其轮廓精灵不会消失.调试发现,角色在死亡时,其引用计数retain_count居然是9.这是由引用计数混乱引起的内存泄露. 加了很多日志跟踪r ...

  3. Solon详解(九)- 渲染控制之定制统一的接口输出

    Solon详解系列文章: Solon详解(一)- 快速入门 Solon详解(二)- Solon的核心 Solon详解(三)- Solon的web开发 Solon详解(四)- Solon的事务传播机制 ...

  4. Solon 框架详解(九)- 渲染控制之定制统一的接口输出

    Springboot min -Solon 详解系列文章: Springboot mini - Solon详解(一)- 快速入门 Springboot mini - Solon详解(二)- Solon ...

  5. Spring框架系列(8) - Spring IOC实现原理详解之Bean实例化(生命周期,循环依赖等)

    上文,我们看了IOC设计要点和设计结构:以及Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的:容器中存放的是Bean的定义即Be ...

  6. Swift详解之NSPredicate

    言:谓词在集合过滤以及CoreData中有着广泛的应用.本文以Playground上的Swift代码为例,讲解如何使用NSPredicate. 准备工作 先在Playground上建立一个数组,为后文 ...

  7. 详解JSP九个内置对象

    [JSP]☆★之详解九个内置对象       在web开发中,为方便开发者,JSP定义了一些由JSP容器实现和管理的内置对象,这些对象可以直接被开发者使用,而不需要再对其进行实例化!本文详解,JSP2 ...

  8. Redis详解(九)------ 哨兵(Sentinel)模式详解

    在上一篇博客----Redis详解(八)------ 主从复制,我们简单介绍了Redis的主从架构,但是这种主从架构存在一个问题,当主服务器宕机,从服务器不能够自动切换成主服务器,为了解决这个问题,我 ...

  9. Git详解之九:Git内部原理

    Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各章一直到这,你都将在本章见识 Git 的内部工作原理和实现方式.我个人发现学习这些内容对于理解 Git 的用处和强大是非常重要的, ...

随机推荐

  1. IT兄弟连 Java Web教程 经典案例2

    案例需求:写一个用户登录的html页面有账号和密码,并在登录的Servlet中获取登录的账号和密码,如果账号是abc密码是123则向浏览器输出登录成功,否则向浏览器输出登录失败. 案例实现: ●  h ...

  2. Typora练习测试

    目录 一级标题 二级标题 三级标题 一级标题 二级标题 三级标题 这是下划线 删除线 字体加粗ctrl+b 这是倾斜线 1111 牛奶 面包 鸡蛋 包子 蛋糕 测试 牛奶 面包 鸡蛋 电脑 鼠标 键盘 ...

  3. Access 中case when then else end不支持使用switch代替

    Access 中case when then else end不支持使用switch代替 这里主要是实现一个表中多个字段,多个字段之间作比较然后取得最大值或者最小值用来处理 case when the ...

  4. nginx命令介绍

    [root@webmaster nginx]# ./sbin/nginx -h nginx version: nginx/1.12.2 Usage: nginx [-?hvVtTq] [-s sign ...

  5. Codeforces Round #410 (Div. 2) A

    Description Mike has a string s consisting of only lowercase English letters. He wants to change exa ...

  6. Codeforces Round #261 (Div. 2) C

    Description Recently Pashmak has been employed in a transportation company. The company has kbuses a ...

  7. npm install error: MSBUILD : error MSB3428: 未能加载 Visual C++ 组件“VCBuild.exe”

    When I tried to run angular 4 material2 demo on my windows server 2012, got a error message: node-pr ...

  8. salt-stack系列报错

    master启动报错 实验环境为: [root@master salt]# cat /proc/version Linux version 3.10.0-327.el7.x86_64 (mockbui ...

  9. sed.exe 在bat中使用时,需要另外起一个文件

    今天在windows使用sed.exe时,同一个文件死活不生效,然后换了一个bat,再来调用,就可以了,怀疑跟sed.exe的代码有关.有时间再研究

  10. 初学web前端,掌握这些就足够了!

    Web开发如今是如日中天,热的发烫.那我们应该怎么学习呢?这不光是初学者,很多学了几年的人也会有些迷茫或者彷徨,大家也都知道不断学习是不可避免的,不学习肯定要掉队:那怎么学效率更高,那些是坑,那些是路 ...