Swift 中的内存管理详解
这篇文章是在阅读《The Swift Programming Language》Automatic Reference Counting(ARC,自动引用计数)一章时做的一些笔记,同时参考了其他的一些资料。
在早期的 iOS 开发中,内存管理是由开发者手动来完成的。因为传统的垃圾回收机制对于移动平台来说十分低效,苹果采用的是引用计数(RC,Reference Counting)的方式来管理内存,开发者需要通过手工的方式增加或减少一个实例的引用计数。在 iOS 5 之后,引入了 ARC 自动引用计数,使得开发者不需要手动地调用 retain
和 release
来管理引用计数,但是实际上这些方法还是会被调用,只不过是交给了编译器来完成,编译器会在合适的地方帮我们加入这些方法。
什么是自动引用计数?
每当你创建一个类的实例的时候,ARC 便会自动分配一块内存空间来存放这个实例的信息,当这个实例不再被使用的时候,ARC 便释放实例所占用的内存。一般每个被管理的实例都会与一个引用计数器相连,这个计数器保存着当前实例被引用的次数,一旦创建一个新的引用指向这个实例,引用计数器便加 1,每当指向该实例的引用失效,引用计数器便减 1,当某个实例的引用计数器变成 0 的时候,这个实例就会被立即销毁。
在 Swift 中,对引用描述的关键字有三个:strong
,weak
和 unowned
,所有的引用没有特殊说明都是 strong
强引用类型。在 ARC 中,只有指向一个实例的所有 strong
强引用都断开了,这个实例才会被销毁。
举一个简单的例子:
class A {let name: Stringinit(name: String) {self.name = name}deinit {print("A deinit")}
}var a1: A?
var a2: A?a1 = A(name: "A")
a2 = a1a1 = nil
上面这个例子中,虽然 a1
这个 strong
强引用断开了,但是还有 a2
这个强引用指向这个实例,所以不会在命令行中输出 A deinit
,当我们把 a2
也设置为 nil
时,与这个实例关联的所有强引用均断开了,这个实例便会被销毁,在命令行中打印 A deinit
。
循环强引用(Strong Reference Cycles)
但是,在某些情况下,一个类实例的强引用数永远不能变为 0,例如两个类实例互相持有对方的强引用,因而每个类实例都让对方一直存在,这就是所谓的强引用循环(Strong Reference Cycles)。
这里引用 TSPL 中的例子:
class Person {let name: Stringinit(name: String) { self.name = name }var apartment: Apartment?deinit { print("\(name) is being deinitialized") }
}class Apartment {let unit: Stringinit(unit: String) { self.unit = unit }var tenant: Person?deinit { print("Apartment \(unit) is being deinitialized") }
}
每一个 Person
实例有一个可选的初始化为 nil
的 Apartment
类型,因为一个人并不总是拥有公寓。同样,每一个 Apartment
实例都有一个可选的初始化为 nil
的 Person
类型,因为一个公寓并不总是属于一个人。
接下来的代码片段定义了两个可选类型的变量 john
和 unit4A
,并分别设定为下面的 Person
和 Apartment
的实例,这两个变量都备受设定为 nil
:
var john: Person?
var unit4A: Apartment?
现在可以创建特定的 Person
和 Apartment
实例,并将它们赋值给 john
和 unit4A
变量:
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
下面一段代码将这两个实例关联起来:
john!.apartment = unit4A
unit4A!.tenant = john
将两个实例关联在一起后,强引用的关系如图所示:
这两个实例关联之后,会产生一个循环强引用,当断开 john
和 unit4A
所持有的强引用时,引用计数器并不会归零,所以这两块空间也得不到释放,这就导致了内存泄漏。
可以将其中一个类中的变量设定为 weak
弱引用来打破这种强引用循环:
class Apartment {let unit: Stringinit(unit: String) { self.unit = unit }weak var tenant: Person?deinit { print("Apartment \(unit) is being deinitialized") }
}
当断开 john
和 unit4A
所持有的强引用时,Person instance
的引用计数器变成 0,实例被销毁,从而 Apartment instance
的引用计数器也变为 0,实例被销毁。
什么时候使用 weak
?
当两个实例是 optional 关联在一起时,确保其中的一个使用 weak
弱引用,就像上面所说的那个例子一样。
unowned
无主引用
在某些情况下,声明的变量总是有值得时候,我们需要使用 unowned
无主引用。
同样借用一下 TSPL 中的例子:
class Customer {let name: Stringvar card: CreditCard?init(name: String) {self.name = name}deinit { print("\(name) is being deinitialized") }
}class CreditCard {let number: UInt64unowned let customer: Customerinit(number: UInt64, customer: Customer) {self.number = numberself.customer = customer}deinit { print("Card #\(number) is being deinitialized") }
}
这里定义了两个类,Customer
和 CreditCard
,模拟了银行客户和客户的信用卡,在这个例子中,每一个类都是将另一个类的实例作为自身的属性,所以会产生循环强引用。
和之前那个例子不同的是,CreditCard
类中有一个非可选类型的 customer
属性,因为,一个客户可能有或者没有一张信用卡,但是一张信用卡总是关联着一个用户。
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
关联两个实例后,它们的引用关系如图所示:
当断开 john
变量持有的强引用时,再也没有指向 Customer
的强引用了,所以该实例被销毁了,其后,再也没有指向 CreditCard
的强引用了,该实例也被销毁了。
什么时候使用 unowned
无主引用?
两个实例 A 和 B,如果实例 A 必须在实例 B 存在的前提下才能存在,那么实例 A 必须用 unowned
无主引用指向实例 B。也就是说,有强制依赖性的那个实例必须对另一个实例持有无主引用。
例如上面那个例子所说,银行客户可能没有信用卡,但是每张信用卡总是绑定着一个银行客户,所以信用卡这个类就需要用 unowned
无主引用。
无主引用以及隐市解析可选属性
还有一种情况,两个属性都必须有值,并且初始化完成之后永远不会为 nil
。在这种情况下,需要一个类使用 unowned
无主引用,另一个类使用隐式解析可选属性。
闭包引起的循环强引用
在 Swift 中,闭包和函数都属于引用类型。并且闭包还有一个特性:可以在其定义的上下文中捕获常量或者变量。所以,在一个类中,闭包被赋值给了一个属性,而这个闭包又使用了这个类的实例的时候,就会引起循环强引用。
Swift 提供了一种方法来解决这个问题:闭包捕获列表(closure capture list)。在定义闭包的同时定义捕获列表作为闭包的一部分,捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例之间的循环强引用一样,声明每个捕获的引用为弱引用或者无主引用。
捕获列表中的每一项都由一对元素组成,一个元素是 weak
或者 unowned
关键字,另一个元素是类实例的引用(例如最常见得是 self
),这些在方括号内用逗号隔开。
具体的使用方法请参考官方文档。
何时使用 weak
,何时使用 unowned
在闭包和捕获的实例总是相互引用并且总是同时销毁的时候,将闭包内的捕获定义为 unowned
无主引用。
在被捕获的实例可能变成 nil
的情况下,使用 weak
弱引用。如果被捕获的引用绝对不会变成 nil
,应该使用 unowned
无主引用,而不是 weak
弱引用。
Garbage Collection(GC,垃圾回收)
其实 ARC 应该也算 GC 的一种,不过我们一谈到 GC,大多都会想到 Java 中的垃圾回收机制,相比较 GC,ARC 简单得许多。以后有机会可以讨论一下 Java 中的内存管理。
另外,需要注意的一点是,这里所讲的都是针对于引用类型
,结构体
和枚举
在 Swift 中属于值类型,不在 ARC 的考虑范围之内。
相关文章:
Ubuntu14.04 64位机上配置OpenCV3.4.2+OpenCV_Contrib3.4.2+Python3.4.3操作步骤
Ubuntu 14.04 64位上默认安装了两个版本的python,一个是python2.7.6,另外一个是python3.4.3。这里使用OpenCV最新的稳定版本3.4.2在Ubuntu上安装python3.4.3支持OpenCV的操作步骤如下: 1. 更新包,执行: sudo apt-get update sud…

“Python之父”从Dropbox退休
作者 | 若名出品 | AI科技大本营(ID:rgznai100)10 月 30 日,Python 之父 Guido Van Rossum 宣布将从工作六年的 Dropbox 公司退休,他在 Twitter 上转发了 Dropbox 团队写的《Thank you, Guido》公开信长文。Guido 表示,…

谭浩强《C++程序设计》书后习题 第十三章-第十四章
2019独角兽企业重金招聘Python工程师标准>>> 最近要复习一下C和C的基础知识,于是计划把之前学过的谭浩强的《C程序设计》和《C程序设计》习题重新做一遍。 编译环境为:操作系统32位Win7,编译工具VC6.0 第十三章:输入输…
图像处理库(fbc_cv):源自OpenCV代码提取
在实际项目中会经常用到一些基本的图像处理操作,而且经常拿OpenCV进行结果对比,因此这里从OpenCV中提取了一些代码组织成fbc_cv库。项目fbc_cv所有的代码已放到GitHub中,地址为 https://github.com/fengbingchun/OpenCV_Test ,它…

Swift2.x编写NavigationController动态缩放titleView
这两天看到一篇文章iOS 关于navigationBar的一些..中的动态缩放比较有意思,看了一下源码,然后用Swift写了一下,使用storyboard实现. 效果图: 部分代码: 设置滑动代理 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/**设置滑动代理- parameter scrollV…

云厂商和开源厂商“鹬蚌相争”,他却看到了开发者的新机会
作者 | 夕颜出品 | AI科技大本营(ID:rgznai100)【导读】过去一年,开发者生态发生了一些或巨大、或微妙的变化,大的变化如巨头云厂商正在通过开源、收购等方式争夺开发者生态,比如微软以 75 亿美金收购 GitHubÿ…

Error: could not open 'D:\Program Files\Java\jre7\lib\amd64\jvm.cfg'
重装JDK后,因为没有装在以前的目录,运行java命令后报错,环境变量的设置都没有问题。解决方法:删除c:/windows/system32/目录下的java.exe 、javaw.exe、javaws.exe,找不到的话在C:\Windows\SysWOW64下找。删除三个文件…
循环神经网络(RNN)简介
人工神经网络介绍参考: https://blog.csdn.net/fengbingchun/article/details/50274471 卷积神经网络介绍参考: https://blog.csdn.net/fengbingchun/article/details/50529500 这里在以上两篇基础上整理介绍循环神经网络: 前馈网络可以…

Swift 中 10 个震惊小伙伴的单行代码
几年前,函数式编程的复兴正值巅峰,一篇介绍 Scala 中 10 个单行函数式代码的博文在网上走红。很快地,一系列使用其他语言实现这些单行代码的文章也随之出现,比如 Haskell,Ruby,Groovy,Clojure&a…

满12万送Mate 30 Pro?华为云“双十一”20+款明星产品齐上线
双十一这次是真的真的真真真来了,华为云11.11血拼风暴一促即发!想好怎么玩转双十一了嘛?怎么买到低价高性价比的云主机?怎么抽到100%中奖的礼品?怎么当欧皇被免单?不仅红包、折扣、特惠、满赠、抽奖一样都没…

javascript json对象转字符串形式
2019独角兽企业重金招聘Python工程师标准>>> /*** json对象转字符串形式*/function json2str(o) {var arr [];var fmt function(s) {if (typeof s object && s ! null) return json2str(s);return /^(string|number)$/.test(typeof s) ? "" …

使用 NSURLSession 开发一个支持后台下载和断点续传的下载工具
NSURLSession 是 iOS 系统提供给我们的原生网络操作库,它提供了网络操作相关的一系列特性支持,比如缓存控制,Cookie管理,HTTP 认证处理等等,是一套整体的网络操作处理解决方案。 关于 NSURLSession 的基本特性…
SSHDroid及sshpass简介
一、SSHDroid简介 SSH为Secure Shell的缩写,是建立在应用层基础上的安全协议。SSH是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用SSH协议可以有效防止远程管理过程中的信息泄露问题。SSH客户端适用于多种平台,几乎所有…

漫画:我用深度学习框架画下女朋友最美的脸
这不,又一个程序员为爱变身灵魂画手,看得我都想学画画了。阿华是幸运的,因为他找到了对的方法,事半功倍,最终有情人终成眷属。这也得益于 PyTorch 的易学易用,大多数人第一次使用 PyTorch 几乎可以做到无痛…
吴恩达老师深度学习视频课笔记:循环神经网络
Why sequence models?:序列数据例子,如下图:(1).语音识别(speech recognition):给定一个输入音频片段X,并要求输出片段对应的文字记录Y,这里输入和输出都是序列数据(sequence data)。因为X是按时序播放的音…

周伯文对话斯坦福AI实验室负责人:下一个NLP前沿是什么?
出品 | AI科技大本营(ID:rgznai100)10 月 31 日,在北京智源大会上,京东集团副总裁兼人工智能事业部总裁、智源-京东联合实验室主任周伯文,斯坦福人工智能实验室负责人(SAIL)Christopher Manning…

IOS8中SWIFT 弹出框的显示
弹出框不管是在网页端,还是在手机APP端,都是常用的控件.在网页中实现个简单的弹出框只需要调用alert,在IOS中,也不是那么复杂,也是容易使用的. 我先用xcode6创建一个名为iOS8SwiftAlertViewTutorial,设置好相关的信息. 在Storyboard中调整好视图显示方式 拖动一个按钮到主视图…
Maven学习笔记(二) :Maven的安装与配置
在Windows上安装Maven: 1. 首先检查安装JDK通过命令行运行命令:echo %JAVA_HOME%和java -version,能够查看当前java的安装文件夹及java的版本号,maven要求JDK的版本号必须在1.4以上。2. 下载Maven前往maven的下载页面:http://ma…

swift闭包
其实闭包就是函数 作为条件的函数 闭包表达式 首先声明一个数组 <code class"hljs cs has-numbering" style"display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: Source Code Pro, mono…
吴恩达老师深度学习视频课笔记:自然语言处理与词嵌入
Word representation:词嵌入(word embedding),是语言表示的一种方式,可以让算法自动理解一些类似的词比如男人、女人,国王、王后等。通过词嵌入的概念,即使你的模型标记的训练集相对较小,也可以构建NLP(自然…

高文院士:为什么中国的AI发展必须要有开源开放平台?
出品 | AI科技大本营(ID:rgznai100)10 月 31 日,由北京智源人工智能研究院主办的 2019 北京智源大会在国家会议中心开幕,本次大会围绕人工智能基础研究现状及面临的机遇和挑战、人工智能技术未来发展的核心方向等话题,…

libcurl断点下载遇到的问题
最近游戏把资源(图片、配置、lua)的加载、更新全部改了 ,加载其实还好,就是不走之前的zip解压方式。 以前的大体流程: 下载 –> 启动 –> 解压 –> 更新 –> 进入游戏 现在的大体流程: 下载 –…

sqlite3数据的使用(xcode 7,ios9)
由于考虑将来还要开发Android版本app,为了移植方便,所以使用了sqlite3来做数据持久化,到时候把sql语句拷过去还能用。 1、 首先用xcode载入sqlite3类库 选择工程的TARGETS-build phases-link binary with libraries,点击“”按钮&…
吴恩达老师深度学习视频课笔记:序列模型和注意力机制
基础模型:比如你想通过输入一个法语句子来将它翻译成一个英语句子,如下图,seq2seq模型,用x<1>一直到x<5>来表示输入句子的单词,然后我们用y<1>到y<6>来表示输出的句子的单词,如何训…

七个开发者成就百亿市值公司?这个技术思路如今让阿里发扬光大
2015年,马云带领阿里巴巴集团的高管拜访了位于芬兰游戏公司supercell 这家公司开发出了《部落战争》、《皇室战争》、《海岛奇兵》等App端知名游戏图片来自多玩BBS社区但是,这么知名的游戏公司开发团队当时却不足7人!整个团队好像cell一样&am…

Linux学习笔记之文件管理和目录管理类命令
在开始理解Linux文件管理和目录类命令之前,有必要先说一下,关于操作系统在计算机中都做了哪些工作。0、操作系统的工作1、文件管理,增删改查2、目录管理3、进程管理4、软件安装5、运行程序6、网络管理7、设备管理本次笔记介绍的是文件管理和目…

张钹、朱松纯、黄铁军等同台激辩:人工智能的“能”与“不能”
整理 | AI科技大本营编辑部出品 | AI科技大本营(ID:rgznai100)10 月 31 日,由北京智源人工智能研究院主办的 2019 北京智源大会在国家会议中心开幕,本次大会吸引了国际人工智能领域的顶级专家学者参加,围绕人工智能基础…

ssqlit3.0数据库使用方法
由于考虑将来还要开发Android版本app,为了移植方便,所以使用了sqlite3来做数据持久化,到时候把sql语句拷过去还能用。 1、 首先用xcode载入sqlite3类库 选择工程的TARGETS-build phases-link binary with libraries,点击“”按钮&…
GCC中通过--wrap选项使用包装函数
在使用GCC编译器时,如果不想工程使用系统的库函数,例如在自己的工程中可以根据选项来控制是否使用系统中提供的malloc/free, new/delete函数,可以有两种方法: (1). 使用LD_PRELOAD环境变量:可以设置共享库的路径&…

[原]对Linux环境下任务调度一点认识
我一直以来有一个误解,那就是在终端运行某个程序时,按下Ctrl D时我误以为就是杀死了这个进程,今天才知道原来不是。比如我利用libevent在Linux环境下写了一个网络监听程序,当启动程序之后,就会一直监听本地的6789端口…