记得学习OC的前期时候内存管理一直是个过不去的坑,虽然我是从ARC开始学起的,但是不管面试,实际开发中特殊问题,还是一些开源的项目大部分还是使用的MRC,导师还是遇到不少的困难,那么Swift重内存管理又是怎样的呢……
作为一种现代化高级编程语言,Swift为您的应用程序中的分配、释放等内存管理需求提供强有力的支持。它使用的是一种称为自动化引用计数(ARC)的技术。通过本文的学习,你将通过以下内容进一步提升你的Swift开发中的ARC编程水平:
1.分配(从堆栈或堆中分配内存)
2.初始化(init代码运行)
3.使用(使用对象)
4.析构(deinit代码运行)
5.释放(内存又回到了堆栈或堆)
虽然没有直接的钩子技术埋伏到内存分配和内存回收中,但是您可以在init和deinit方法中 使用print语句作为代理手段来监控上述过程。注意,尽管上面过程4和5中的释放和析构两个方法常常交替使用,但实际上它们是在一个对象的生命周期中的两个不同的阶段。
引用计数是当不再需要对象时被释放的机制。这里的问题是:“你何时可以肯定未来不会需要这个对象?”通过保持一个使用次数的统计计数,即“引用计数”即可实现这一管理目的。引用计数存储于每一个对象实例内部。
上面的计数能够确定,有多少“东西”引用了对象。当一个对象的引用计数降为零时,也就是说对象的客户端不再存在;于是,对象被析构和解除内存分配
ARC与GC
很多人分不清ARC(Automatic Reference Counting,自动引用计数)跟GC(Garbage Collection,垃圾收集)的区别。其实“引用计数法”也算是一种GC策略,只不过我们现在提到GC的时候一般是指基于“标记-整理”策略的垃圾收集器,譬如主流的JVM(Java虚拟机)几乎都是采用“标记-整理”+“分代收集”的策略来进行自动内存管理的。标记算法一般是从全局对象图的“根”出发进行可达性分析,对象的生死会被批量地标记出来,之后再在某个时间批量地释放死对象。显然,这是一种“全局+延时”的管理策略。
而与之相对的,引用计数是一种“局部+即时”的内存管理策略。它不需要全局的对象信息,一般每个被管理的对象都会跟一个引用计数器关联,这个计数器保存着当前对象被引用的次数,一旦创建一个新的引用指向该对象,引用计数就加1,每当指向该对象的某个引用失效引用计数就减1,直到引用计数为0,就立即释放该对象。使用引用计数法管理内存的语言也不止OC和Swift,还有诸如CPython之类的GC也是基于引用计数的。
早年OC是采用MRC(手动引用计数)的,当然其实现在也有人还在用,它跟ARC的主要区别在于它需要手动管理引用计数器,而ARC是自动管理的。所以其实MRC也不能让你直接释放对象的,只是控制引用罢了。
ARC的功能
在每一次一个新的类实例被创建时ARC分配一块内存以存储信息 init()
关于实例类型和其值的信息存储在存储器中
当类实例不再需要它自动由 deinit() 释放,用于进一步类实例的存储和检索的存储空间
ARC保存在磁道当前参照类实例的属性,常量和变量,使得 deinit() 仅适用于那些不使用的实例。
ARC维护“强引用”这些类实例属性,常量和变量来限制释放当当前的类实例正在使用。
Swift中内存管理包括哪些呢?
1、内存管理,weak和unowned
2、@autoreleasepool
3、C 指针内存管理
1、内存管理,weak和unowned
1.Swift中的unowned等效于OC中的unsafe_unretained,而Swift中的weak就是OC中的weak。如果能够确定在访问时不会已被释放,就尽量使用unowned;如果存在被释放的可能,那就选择用weak。
2.被标记为weak的变量一定需要是Optional值。
3.闭包和循环引用
lazy var printName: () —> () = {
[weak self] in
if let strongSelf = self {
print(“The name is \(strongSelf.name)”)
}
} //self持有闭包,闭包又持有self,循环引用。为了解决这种闭包内的循环引用,我们需要在闭包开始的时候添加一个标注,来表示这个闭包内的某些要素应该以何种特定的方式来使用,比如像这里的[weak self]。
这种在闭包参数的位置进行标注的语法结构是将要标注的内容放在原来参数的前面,并使用括号括起来。如果有多个需要标注的元素的话,在同一个中括号内用逗号隔开。
{ [unowned self, weak someObject] (number: Int) —> Bool in
//…… return true
}
2、@autoreleasepool
Swift中的自动释放池的创建语法:
autoreleasepool {
let data = NSData.dataWithContentsOfFile(path, options: nil, error: nil) //dataWithContentsOfFile返回的autorelease对象。在Swift中更提倡的是用初始化方法而不是用像上面那样的类方法来生成对象,而且从Swift 1.1开始,因为加入了可以返回nil的初始化方法,像上面例子中那样的工厂方法都已经从API中删除了。
}
let data = NSData(contentsOfFile: path) //使用初始化方法的话,我们就不需要面临自动释放的问题了,每次在超过作用域后,自动内存管理都将为我们处理好内存相关的事情。
3、C 指针内存管理
在Swift中C指针的内存需要自己手动管理。
class Employee { ①
var no : Int
var name : String
var job : String
var salary : Double
init(no : Int, name: String, job : String, salary : Double) { ②
self.no = no
self.name = name
self.job = job
self.salary = salary
println("员工\(name) 已经构造成功。") ③
}
deinit { ④
println("员工\(name) 已经析构成功。") ⑤
}
}
var ref1: Employee? ⑥
var ref2: Employee? ⑦
var ref3: Employee? ⑧
ref1 = Employee(no: 7698, name: "Blake", job :"Salesman", salary : 1600) ⑨
ref2 = ref1 ⑩
ref3 = ref1 ⑪
ref1 = nil ⑫
ref2 = nil ⑬
ref3 = nil ⑭
代码解释
上述代码第①行声明了Employee类,第②行代码是定义构造器,在构造器中初始化存储属性,并且在代码第③行输出构造成功信息。第④行代码是定义析构器,并在代码第⑤行输出析构成功信息。
代码第⑥~⑧行是声明3个Employee类型变量,这个时候还没有创建Employee对象分配内存空间。代码第⑨行是真正创建Employee对象分配内存空间,并把对象的引用分配给ref1变量,ref1与对象建立“强引用”关系,“强引用”关系能够保证对象在内存中不被释放,这时候它的引用计数是1。第⑩行代码ref2 = ref1是将对象的引用分配给ref2,ref2也与对象建立“强引用”关系,这时候它的引用计数是2。第⑪行代码ref3 = ref1是将对象的引用分配给ref3,ref3也与对象建立“强引用”关系,这时候它的引用计数是3。
然后在代码第⑫行通过ref1 = nil语句断开ref1对Employee对象的引用,这时候它的引用计数是2。以此类推,ref2 = nil时它的引用计数是1,ref3 = nil时它的引用计数是0,当引用计数为0的时候Employee对象被释放。
我们可以测试一下看看效果,如果设置断点单步调试,会发现代码运行完第⑨行后控制台输出:
员工Blake 已经构造成功。
析构器输出的内容直到运行完第⑭行代码才输出:
员工Blake 已经析构成功。
这说明只有在引用计数为0的情况下才调用析构器,释放对象。
在 Swift 中,对引用描述的关键字有三个:strong,weak 和 unowned,所有的引用没有特殊说明都是 strong 强引用类型。在 ARC 中,只有指向一个实例的所有 strong 强引用都断开了,这个实例才会被销毁。
循环强引用(Strong Reference Cycles)
但是,在某些情况下,一个类实例的强引用数永远不能变为 0,例如两个类实例互相持有对方的强引用,因而每个类实例都让对方一直存在,这就是所谓的强引用循环(Strong Reference Cycles)。
什么时候使用 weak?
当两个实例是 optional 关联在一起时,确保其中的一个使用 weak 弱引用,就像上面所说的那个例子一样。
unowned 无主引用
在某些情况下,声明的变量总是有值得时候,我们需要使用 unowned 无主引用。
什么时候使用 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 的考虑范围之内。
Swift内存管理机制总结
swift同样使用自动计数(ARC)会制动释放内存。swift有3种引用方式:
在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以阻止强引用环。如果整个生命周期内引用都有值,那么相应的用无主引用,
强引用 会增加实例的引用计数。
弱引用 弱引用不会增加实例的引用计数,因此不会阻止ARC销毁被引用的实例。这种特性使得引用不会变成强引用环。声明属性或者变量的时候,关键字weak表明引用为弱引用
无主引用 和弱引用相似,无主引用也不强持有实例。但是和弱引用不同的是,无主引用默认始终有值。
最后贴上一段使用的demo
import UIKit
class Person: NSObject {
var name: String?
var age: Int?
// block 可能的循环引用
var block: ((Void)->Void)? = nil
override init() {
super.init()
// weak 只能修改变量 ,其值在运行期间会被修改
// 闭包造成的循环引用([unowned self, weak delegate = self.delegate] 无主引用解决这个问题)
// 遇见代理的时候我们还需要weak来修饰。注意内存泄漏
weak var weakself = self;
self.block = { [unowned self] in
weakself?.name = "修改";
}
self.block!()
}
deinit {
// 动态绑定, 因为第51行(p3)创建出来的对象没有name, self.name强制拆包会崩溃
if let name = self.name {
print(name + "被解放了");
}
}
}
class ViewController: UIViewController {
var name: String?
override func viewDidLoad() {
super.viewDidLoad()
// 引用计数
var p = Person() // 0 -> 1
p.name = "小王"
var p1 = p // 1 -> 2
var p2 = p // 2 -> 3
// 因为类型不匹配,无法赋值nil. 只能修改指针的指向让p释放
let p3 = Person()
p1 = p3 // 3 -> 2
p2 = p3 // 2 -> 1
p = p3
}
// 两个类相互引用
// var stu = student()
// var tea = teachet()
// tea.student = stu
// student.tea = tea
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
对于闭包内的引用,何时使用弱引用,何时使用无主引用呢?
如果在一个值的生命周期中可能没有值,我们就使用weak,weak可选 ,weak必须被申明为变量,在运行时可能被修改
如果一只值在生命周期一只有值,我们使用无主引用 , 无主是非可选类型 ,非可选类型不能赋值为nil
*/
}