弱引用?强引用?未持有?额滴神啊-- Swift 引用计数指导
ARC
ARC 苹果版本的自动内存管理的编译时间特性。它代表了自动引用计数(Automatic Reference Counting)。也就是对于一个对象来说,只有在引用计数为0的情况下内存才会被释放。
Strong(强引用)
让我们从什么是强引用说起。它实质上就是普通的引用(指针等等),但是它的特殊之处在于它能够通过使对象的引用计数+1来保护对象,避免引用对象被ARC机制销毁。本质上来讲,任何对象只要有强引用,它就不会被销毁掉。记住这点对我接下来要讲的引用循环等其他知识来说很重要。
强引用在swift中无处不在。事实上,当你声明一个属性时,它就默认是一个强引用。一般来说,当对象之间的关系为线性时,使用强引用是安全的。当对象之间的强引用是从父层级流向子层级时,用强引用通常也是ok的。
下面是一些强引用的例子
1
2
3
4
5
6
7
8
9
|
class Kraken { let tentacle = Tentacle() //strong reference to child. } class Tentacle { let sucker = Sucker() //strong reference to child } class Sucker {} |
示例代码展示了线性关系。Kraken有一个指向Tentacle实例对象的强引用,而Tentacle又有一个指向Sucker实例对象的强引用。引用关系从父层级(Kraken)一直流到子层级(Sucker)。
同样的,在动画blocks中,引用关系也类似:
1
2
3
|
UIView.animateWithDuration(0.3) { self.view.alpha = 0.0 } |
由于animateWithDuration是UIView的一个静态方法,这里的闭包作为父层级,self作为子层级。
那么当一个子层级想引用父层级会怎么样呢?这里我们就要用到 weak 和 unowned 引用了。
Weak 和 unowned 引用
weak
weak 引用并不能保护所引用的对象被ARC机制销毁。强引用能使被引用对象的引用计数+1,而弱引用不会。此外,若弱引用的对象被销毁后,弱引用的指针会被清空。这样保证了当你调用一个弱引用对象时,你能得到一个对象或者nil.
在swift中,所有的弱引用都是非常量的可选类型(对比 var 和 let) ,因为当没有强引用对象引用的的时候,弱引用对象能够并且会变成nil。
例如,这样的代码不会通过编译
1
2
3
|
class Kraken { weak let tentacle = Tentacle() //let is a constant! All weak variables MUST be mutable. } |
因为tentacle是一个let常量。Let常量在运行的时候不能被改变。因为弱引用变量在没有被强引用的条件下会变成nil,所以Swift 编译器要求你必须用var来定义弱引用对象。
值得注意的地方是,使用弱引用变量能够避免你出现可能的引用循环。当两个对象相互强引用的时候会出现一个引用循环。如果2个对象相互引用对方,ARC就不能给这两个对象发出合适的释放信息,因为这两个对象彼此相互依存。下图是从苹果官方简洁的图片,它很好的解释了这种情况:
一个比较恰当的例子就是通知APIs,看一下下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Kraken { var notificationObserver: ((NSNotification) -> Void)? init() { notificationObserver = NSNotificationCenter.defaultCenter().addObserverForName( "humanEnteredKrakensLair" , object: nil, queue: NSOperationQueue.mainQueue()) { notification in self.eatHuman() } } deinit { if notificationObserver != nil { NSNotificationCenter.defaultCenter.removeObserver(notificationObserver) } } } |
在这种情况下我们有一个引用循环。你会发现,Swift中的闭包的表现类似与Objective-C的blocks。如果在闭包范围之外声明变量,那么在闭包中使用这个变量时,会对该变量产生另一个强引用。唯一的例外是使用值类型的变量,比如Swift中的 Ints、Strings、Arrays以及Dictionaries等。
在这里,当你调用eatHuman( ) 时,NSNotificationCenter就保留了一个闭包以强引用方式捕获self。经验告诉我们,你应该在deinit方法中清除通知监听对象。这段代码的问题在于我们没有清除掉block直到deinit.但是deinit 永远都不会被ARC机制调用,因为闭包对Kraken实例有强引用。
另外在NSTimers和NSThread也可能会出现这种情况。
解决这种情况的方法就是在闭包的捕获列表中使用对self的弱引用。这样就能够打破强引用循环。那么,我们的对象引用图就会像这样:
把self变成weak不会让self 的引用计数+1,因此ARC机制就能在合适的时间释放掉对象。
想要在闭包使用 weak 和 unowned 变量,你应该用[]把它们括起来。如:
1
2
3
|
let closure = { [weak self] in self?.doSomething() //Remember, all weak variables are Optionals! } |
在上面的代码中,为什么要把 weak self 要放在方括号内?看上去简直秀逗了!在Swift中,我们看到方括号就会想到数组。你猜怎么着?你可以在在闭包内定义多个捕获值!例如:
1
2
3
4
|
let closure = { [weak self, unowned krakenInstance] in //Look at that sweet Array of capture values. self?.doSomething() //weak variables are Optionals! krakenInstance.eatMoreHumans() //unowned variables are not. } |
这样看上去更像是数组了,对吧?现在你知道为什么把捕获值放在方括号里面了吧。那么用我们已了解的东西,通过在闭包捕获列表中加上[weak self],我们就可以解决之前那段有引用循环的通知代码。
1
2
3
|
NSNotificationCenter.defaultCenter().addObserverForName( "humanEnteredKrakensLair" , object: nil, queue: NSOperationQueue.mainQueue()) { [weak self] notification in //The retain cycle is fixed by using capture lists! self?.eatHuman() //self is now an optional! } |
其他我们用到weak和unowned变量的情况是当你使用协议在多个类之间实现代理时,因为Swift中类使用的是reference semantics。在Swift中,结构体和枚举同样能够遵循协议,但是它们用的是value semantics。如果像这样一个父类带上一个子类使用委托使用了代理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Kraken: LossOfLimbDelegate { let tentacle = Tentacle() init() { tentacle.delegate = self } func limbHasBeenLost() { startCrying() } } protocol LossOfLimbDelegate { func limbHasBeenLost() } class Tentacle { var delegate: LossOfLimbDelegate? func cutOffTentacle() { delegate?.limbHasBeenLost() } } |
在这里我们就需要用weak变量了。在这种情况下,Tentacle以代理属性的形式对Kraken有着一个强引用,而Kraken在它的Tentacle属性中对Tentacle也有一个强引用。我们通过在代理声明前面加上weak来解决这个问题:
1
|
weak var delegate: LossOfLimbDelegate? |
是不是发现这样写不能通过编译?不能通过编译的原因是非 class类型的协议不能被标识成weak。这里,我们必须让协议继承:class,从而使用一个类协议将代理属性标记为weak。
1
2
3
|
protocol LossOfLimbDelegate: class { //The protocol now inherits class func limbHasBeenLost() } |
我们什么时候用 :class,通过苹果官方文档:
“Use a class-only protocol when the behavior defined by that protocol’s requirements assumes or requires that a conforming type has reference semantics rather than value semantics.”
本质上来讲,当你有着跟我上述代码一样的引用关系,你就用:class。在结构体和枚举的情况下,没有必要用:class,因为结构体和枚举是value semantics,而类是 reference semantics.
UNOWNED
weak引用和unowned引用有些类似但不完全相同。Unowned 引用,像weak引用一样,不会增加对象的引用计数。然而,在Swift里,一个unowned引用有着非可选类型的优点。这样相比于借助和使用optional binding更易于管理。这和隐式可选类型(Implicity Unwarpped Optionals)区别不大。此外,unowned引用是non-zeroing(非零的) ,这表示着当一个对象被销毁时,它指引的对象不会清零。也就是说使用unowned引用在某些情况下可能导致 dangling pointers(野指针url)。你是不是跟我一样想起了用Objective -C的时候, unowned引用映射到了 unsafe_unretained引用。 http://www.krakendev.io/when-to-use-implicitly-unwrapped-optionals/
看到这里是不是有点蛋疼了。既然Weak和unowned引用都不会增加引用计数,它们都能用于解除引用循环。那么我们该在什么使用它们呢?根据苹果文档:
“Use a weak reference whenever it is valid for that reference to become nil at some point during its lifetime. Conversely, use an unowned reference when you know that the reference will never be nil once it has been set during initialization.”
翻译:在引用对象的生命周期内,如果它可能为nil,那么就用weak引用。反之,当你知道引用对象在初始化后永远都不会为nil就用unowned.
现在你就知道了:就像是implicitly unwrapped optional(隐式可选类型),如果你能保证在使用过程中引用对象不会为nil,用unowned 。如果不能,那么就用weak
下面就是个很好的例子。Class 里面的闭包捕获了self,self永远不会为nil。
1
2
3
4
5
6
7
8
9
10
11
12
|
class RetainCycle { var closure: (() -> Void)! var string = "Hello" init() { closure = { self.string = "Hello, World!" } } } //Initialize the class and activate the retain cycle. let retainCycleInstance = RetainCycle() retainCycleInstance.closure() //At this point we can guarantee the captured self inside the closure will not be nil. Any further code after this (especially code that alters self's reference) needs to be judged on whether or not unowned still works here. |
在这种情况下,闭包用强引用形式捕获了self,而self也通过闭包属性保留了一个对闭包的强引用,这就出现了引用循环。只要给闭包添加[unowned self] 就能打破引用循环:
1
2
3
|
closure = { [unowned self] in self.string = "Hello, World!" } |
在这个例子中,由于我们在初始化RetainCycle类后立即调用了闭包,所以我们可以认为self永远不会为nil。
苹果在unowned references也说明了这点:
“Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.”
如果你知道你引用的对象会在正确的时机释放掉,且它们是相互依存的,而你不想写一些多余的代码来清空你的引用指针,那么你就应该使用unowned引用而不是weak引用。
像下面这种懒加载在闭包中使用self就是一个使用unowned的很好例子:
1
2
3
4
5
6
|
class Kraken { let petName = "Krakey-poo" lazy var businessCardName: () -> String = { [unowned self] in return "Mr. Kraken AKA " + self.petName } } |
我们需要用unowned self 来避免引用循环。Kraken 和 businessCardName在它们的生命周期内都相互持有对方。它们相互持有,因此总是被同时销毁,满足使用unowned 的条件。
然而,不要把下面的懒加载变量与闭包混淆:
1
2
3
4
5
6
|
class Kraken { let petName = "Krakey-poo" lazy var businessCardName: String = { return "Mr. Kraken AKA " + self.petName }() } |
在懒加载变量中调用closure时,由于没有retain closure,所以不需要加 unowned self。变量只是简单的把闭包的结果assign 给了自己,闭包在使用后就被立即销毁了。下面的截图很好的证明了这点。(截图是厚着脸皮评论区Алексей的拷贝)
总结
引用循环很坑爹!但是谨慎码代码,理清你的引用逻辑,通过使用weak 和 unowned 能够很好的避免内存循环和 内存泄露。我希望这篇指导手册能够帮到你们。
弱引用?强引用?未持有?额滴神啊-- Swift 引用计数指导的更多相关文章
- Java的四种引用——强引用、软引用、弱引用、虚引用
目录 强引用 软引用 弱引用 虚引用 强引用 拥有强引用的对象永远不会被GC,可以根据引用的get方法获取到被引用对象 软引用 在内存充足的额时候,拥有软引用的对象不会被GC:即将内存溢出的时候,会对 ...
- Java对象引用/JVM分级引用——强引用、软引用、弱引用、虚引用
无论是通过引用计数法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判断对象是否存活都与“引用”有关, 相关资料:如何判断对象是否存活/死去 那么引用究竟是什么?让我们一起来看一下 ...
- java的4种引用 强软弱虚
<img src="https://pic4.zhimg.com/d643d9ab5c933ac475cfa23063bed137_b.png" data-r ...
- java的四种引用:强软弱虚
简介 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有对象处于(reachable)可达状态,程序才能使用它. 从JDK 1.2版本开始,对象的引 ...
- Swift引用计数器
ARC概述 和4.2+版本的Xcode对OC的支持一样,Swift也是使用ARC来管理内存,文档是这么描述的: Swift uses Automatic Reference Counting(ARC) ...
- SaaS架构(一) 弱后端强前端的尝试和问题
最近在公司项目组内部沙龙的时候,提出一个"弱后端强前端"的概念,其实已经在项目内部新的服务有做试点,我们整个SaaS系统,后端主要是JAVA构建,前端是Angular构建.&quo ...
- 使用 EPPlus 封装的 excel 表格导入功能 (二) delegate 委托 --永远滴神
使用 EPPlus 封装的 excel 表格导入功能 (二) delegate 委托 --永远滴神 前言 接上一篇 使用 EPPlus 封装的 excel 表格导入功能 (一) 前一篇的是大概能用但是 ...
- node-ejs-mongodb结合的项目案例-----引用mongoose和未引用mongoose模块
本项目个人尝试了2种方法,一个是直接用mongod,一个是引用mongod里的mongoose. nodejs-ejs-mogondb- nodej+ejs模板,通过mogondb数据查询数据实现简单 ...
- const constptr 和引用的盲点(未解决)
#include<iostream> //const 和 引用的值必须初始化 //等号左侧是const或者const和引用,右侧可以是数字,普通变量-等号左侧是const和指针,右侧必须是 ...
随机推荐
- jq prepend() 方法在被选元素的开头(仍位于内部)插入指定内容。 提示:prepend() 和 prependTo() 方法作用相同。差异在于语法:内容和选择器的位置,以及 prependTo() 无法使用函数来插入内容。
<html><head><script type="text/javascript" src="/jquery/jquery.js" ...
- Android 定位地理坐标体系
参考: 中国特色 火星坐标 iOS 火星坐标相关整理及解决方案汇总 百度地图坐标转换API 地球坐标系 (WGS-84) 到火星坐标系 (GCJ-02)百度坐标系 (BD-09) 的转换算法 火星坐标 ...
- 【POJ】3744 Scout YYF I
http://poj.org/problem?id=3744 题意:直线上n个地雷,n<=10,范围在[1, 100000000],每一次有p的概率向前走一步,1-p的概率向前走两步,问安全通过 ...
- ArcGIS 裁剪地图显示范围
在argmap工具中,图层属性中,数据框选择“裁剪选项”,“指定范围”,根据一个要素的轮廓,即可以选择需要全屏显示的图层“要素的轮廓”,确定以后地图就自动居中显示,请注意要排除掉超出范围的图层,否则发 ...
- 史上最全的Win8快捷键大全
下列的 Win8 快捷键列表汇总均收集自网络,未全部实测,也有可能有Win7时代的热键混迹其中,不管怎样,如有错漏,欢迎大家指正! Win8 常用快捷键: Win键 可在开始屏幕主菜单及最后一个应用程 ...
- IE6及以上版本fixed问题解决方案,页面右下角固定页面,可以最大化、最小化、正规显示
在窗口固定位置显示内容使用fixed,但是 IE 6 不支持,后来我搜了很多方法,都没有作用,后来类比着一个网站的代码,使用absolute .z-index解决了问题. 页面div结构: <d ...
- javascrit2.0完全参考手册(第二版) 第2章第4节 基本的数据类型
每一个变量都有一个确定的类型表明它存储什么样的数据.js基本的数据类型有strings字符串.numbers数字.Booleans布尔类型.字符串是使用双引号或单引号包含的一串字符:数字包括整数或浮点 ...
- iOS - JSON 数据解析
iOS - JSON 数据解析 前言 NS_CLASS_AVAILABLE(10_7, 5_0) @interface NSJSONSerialization : NSObject @availab ...
- [LintCode] Super Ugly Number 超级丑陋数
Write a program to find the nth super ugly number. Super ugly numbers are positive numbers whose all ...
- Hadoop.2.x_伪分布环境搭建
一. 基本环境搭建 1. 设置主机名.静态IP/DNS.主机映射.windows主机映射(方便ssh访问与IP修改)等 设置主机名: vi /etc/sysconfig/network # 重启系统生 ...