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 引用计数指导的更多相关文章

  1. Java的四种引用——强引用、软引用、弱引用、虚引用

    目录 强引用 软引用 弱引用 虚引用 强引用 拥有强引用的对象永远不会被GC,可以根据引用的get方法获取到被引用对象 软引用 在内存充足的额时候,拥有软引用的对象不会被GC:即将内存溢出的时候,会对 ...

  2. Java对象引用/JVM分级引用——强引用、软引用、弱引用、虚引用

    无论是通过引用计数法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判断对象是否存活都与“引用”有关, 相关资料:如何判断对象是否存活/死去 那么引用究竟是什么?让我们一起来看一下 ...

  3. java的4种引用 强软弱虚

    <img src="https://pic4.zhimg.com/d643d9ab5c933ac475cfa23063bed137_b.png" data-r ...

  4. java的四种引用:强软弱虚

    简介 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有对象处于(reachable)可达状态,程序才能使用它. 从JDK 1.2版本开始,对象的引 ...

  5. Swift引用计数器

    ARC概述 和4.2+版本的Xcode对OC的支持一样,Swift也是使用ARC来管理内存,文档是这么描述的: Swift uses Automatic Reference Counting(ARC) ...

  6. SaaS架构(一) 弱后端强前端的尝试和问题

    最近在公司项目组内部沙龙的时候,提出一个"弱后端强前端"的概念,其实已经在项目内部新的服务有做试点,我们整个SaaS系统,后端主要是JAVA构建,前端是Angular构建.&quo ...

  7. 使用 EPPlus 封装的 excel 表格导入功能 (二) delegate 委托 --永远滴神

    使用 EPPlus 封装的 excel 表格导入功能 (二) delegate 委托 --永远滴神 前言 接上一篇 使用 EPPlus 封装的 excel 表格导入功能 (一) 前一篇的是大概能用但是 ...

  8. node-ejs-mongodb结合的项目案例-----引用mongoose和未引用mongoose模块

    本项目个人尝试了2种方法,一个是直接用mongod,一个是引用mongod里的mongoose. nodejs-ejs-mogondb- nodej+ejs模板,通过mogondb数据查询数据实现简单 ...

  9. const constptr 和引用的盲点(未解决)

    #include<iostream> //const 和 引用的值必须初始化 //等号左侧是const或者const和引用,右侧可以是数字,普通变量-等号左侧是const和指针,右侧必须是 ...

随机推荐

  1. Excel 如何引用某表格中的某一列作为数据有效性验证

    1. 首先把数据有效性的列表加入到某个表格中.如下图所示:此表格名称为表5 2. 然后定义名称:公式--定义名称 如下填入信息: 3. 然后再数据有效性验证中输入如下信息即可:

  2. 种树 & 乱搞

    题意: 在一个(n+1)*(m+1)的网格点上种k棵树,树必须成一条直线,相邻两棵树距离不少于D,求方案数. SOL: 这题吧...巨坑无比,本来我的思路是枚举每一个从(0,0)到(i,j)的矩形,然 ...

  3. maven 仓库地址:

    maven 仓库地址: 共有的仓库 http://repo1.maven.org/maven2/http://repository.jboss.com/maven2/ http://repositor ...

  4. NOIp 2014 #5 解方程 Label:数论?

    题目描述 已知多项式方程: a0+a1x+a2x^2+..+anx^n=0 求这个方程在[1, m ] 内的整数解(n 和m 均为正整数) 输入输出格式 输入格式: 输入文件名为equation .i ...

  5. NOI OpenJudge 8469 特殊密码锁 Label贪心

    描述 有一种特殊的二进制密码锁,由n个相连的按钮组成(n<30),按钮有凹/凸两种状态,用手按按钮会改变其状态. 然而让人头疼的是,当你按一个按钮时,跟它相邻的两个按钮状态也会反转.当然,如果你 ...

  6. 定时器的fireDate指的是触发时间

    1.定时器开启后,会在经过设定的时间间隔后才会执行第一次定时操作.而不是立马开启. NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: ...

  7. Facebook通过oAuth验证获取json数据

    首先下载facebook相关的动态库,下载文件:facebook.dll 获取授权token方法: private string SetToken(string gettoken)//此处是你的短to ...

  8. 让wego微购购物分享系统采集拍拍数据功能之腾讯paipai功能采集插件

    wego是一款很不错的导购系统,无论前后台设计风格和功能都还不错,可有时我们的确需要一些自定义的功能,毕竟万千世界,大家都做一样的东西,采集同样的数据,能不烦吗?哈哈,今天就奉献上一个wego拍拍采集 ...

  9. Centos 安装了 Wkhtmltopdf 却依旧显示 无法打印pdf

    Odoo里判断wkhtmlpdf是否安装的代码在 openerp/tools/misc.py 文件中: def find_in_path(name): path = os.environ.get('P ...

  10. CentOS安装开发组相关的包

    yum groupinstall "Development Tools"   yum groupremove "Development Tools"