swift详解之九---------------自动引用计数、循环引用

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

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


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);

时间: 2024-10-14 12:38:00

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

Git详解之九 Git内部原理

来自:http://www.open-open.com/lib/view/open1328070620202.html Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各章一直到这,你都将在本章见识 Git 的内部工作原理和实现方式.我个人发现学习这些内容对于理解 Git 的用处和强大是非常重要的,不过也有人认为这些内容对于初学者来说可能难以理解且过于复杂.正因如此我把这部分内容放在最后一章,你在学习过程中可以先阅 读这部分,也可以晚点阅读这部分,这完全取决于你自己. 既然已

Git详解之九:Git内部原理

Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各章一直到这,你都将在本章见识 Git 的内部工作原理和实现方式.我个人发现学习这些内容对于理解 Git 的用处和强大是非常重要的,不过也有人认为这些内容对于初学者来说可能难以理解且过于复杂.正因如此我把这部分内容放在最后一章,你在学习过程中可以先阅 读这部分,也可以晚点阅读这部分,这完全取决于你自己.(伯乐在线注:如果你对Git还不了解,建议从本Git系列第一篇文章开始阅读) 既然已经读到这了,就让我们开始吧.首先要弄明白一点

swift详解之十九--------------UITableView的基本操作(下拉刷新,新增删除,分组,检索等)

UITableView的基本操作(下拉刷新,新增删除,分组,检索等) 注:本小结总结UITableview的一些基本用法 UITbleView继承自UIScrollView,只能用来显示一列数据(目前就只认识到这里),纵向滑动. 一般有两种方式来实现,直接用UITableViewController , 占满整个屏幕 .不用手动实现UITableViewDataSource 和UITableViewDelegate .另一种方式在UIViewController 中.我们看看这种方式 let t

Swift详解之四-------妈妈再也不用担心我的闭包了

妈妈再也不用担心我的闭包了 注:本文为作者自己总结,过于基础的就不再赘述 ,都是亲自测试的结果.如有错误或者遗漏的地方,欢迎指正,一起学习. swift中闭包是一个很强大的东西,闭包是自包含的函数代码块,可以在代码中被传递和使用.跟C 和 Objective-C 中的代码块(blocks)很相似 .这个大家必须掌握!必须掌握!必须掌握!重要的事情要说三遍 闭包可以捕获和存储其所在上下文中任意常量和变量的引用. 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包.下面我们就来攻克它! 1.闭包函数

swift详解之十-------------异常处理、类型转换 ( Any and AnyObject )

异常处理.类型转换 ( Any and AnyObject ) 1.错误处理 (异常处理) swift 提供第一类错误支持 ,包括在运行时抛出 ,捕获 , 传送和控制可回收错误.在swift中 ,错误用复合 ErrorType 协议的值表示 . Swift枚举把一系列相关的错误组合在一起.同时可以把一些相关的值和错误关联在一起 . 因此 编译器会为实现ErrorType协议的Swift枚举类型自动实现相应的合成 这里看个很简单的小例子. enum WrongName:ErrorType{ cas

Swift 详解之一 -------- 变量声明

变量声明 1.普通变量 swift 像javascript一样是可以智能推断类型的,他会根据我们变量的值来推断变量的类型 var age = 10 // Int var name ="lily" //编译器会自动推断出String static let name = "MyClass" //静态常量 通过类名直接访问 eg: ClassName.name 2.存储型变量或者存储型属性 语法:var variable name: type = expression 例

swift详解之十四 -----------NSThread 异步下载图片

NSThread 异步下载图片 在IOS中处理多线程有三个方案 , NSThread .NSOperation .GCD .当然GCD应该是最方便的 ,我们一个一个学 .先理解底层的,最后再使用最方便的 . NSThread: 优点:NSThread 比其他两个轻量级 缺点:需要自己管理线程的生命周期,线程同步.线程同步对数据的加锁会有一定的系统开销 我们先研究下用NSThread 异步加载网络图片 : NSThread有两种创建方式 ,一种是通过实例方法.一种是通过类方法. let threa

swift详解之十二-----------------泛型

泛型 注:本文详细讲解泛型,对写一些公共方法,共用的东西的时候很有帮助.想成为一个优秀的程序员吗?这个必须掌握 泛型代码可以让你写出根据自我需求定义.适用于任何类型的,灵活可重用的函数和类型. 下面直接看一个简单的示例 , 泛型其实就是在写的时候不指定是哪种类型 , 只有再用得时候才指定类型 ,这些类型可以给一些限定.例如 Array Dictionary 等 写一个简单的交换数据的方法,不限定类型 func swapTwoValue<T>(inout a:T,inout _ b:T){ le

Swift详解之三----------函数(你想知道的都在这里)

函数(你想知道的都在这里) 注:本文为作者自己总结,过于基础的就不再赘述 ,都是亲自测试的结果.如有错误或者遗漏的地方,欢迎指正,一起学习. 1. 函数的简单定义和调用 简单的无参函数就不再赘述 , name为形参 ,也是内部在数名 . func sayHello(name:String) ->String { return name+" say: hello" } 调用的时候也很简单 sayHello("zhangsan") 是不是很简单呀! 2.外部参数名