生活随笔
收集整理的这篇文章主要介绍了
初探swift语言的学习笔记六(ARC-自动引用计数,内存管理)
小编觉得挺不错的,现在分享给大家,帮大家做个参考.
作者:fengsh998
原文地址:http://blog.csdn.net/fengsh998/article/details/31824179
转载请注明出处
如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号fengsh998来支持我,谢谢!
Swift使用自动引用计数(ARC)来管理应用程序的内存使用。这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理。当实例并不再被需要时,ARC会自动释放这些实例所使用的内存。
另外需要注意的:
引用计数仅仅作用于类实例上。结构和枚举是值类型,而非引用类型,所以不能被引用存储和传递。
swift的ARC工作过程
每当创建一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息。
另外当实例不再被使用时,ARC会释放实例所占用的内存,这些内存可以再次被使用。
但是,如果ARC释放了正在被使用的实例,就不能再访问实例属性,或者调用实例的方法了。直接访问这个实例可能造成应用程序的崩溃。就像空实例或游离实例一样。
为了保证需要实例时实例是存在的,ARC对每个类实例,都追踪有多少属性、常量、变量指向这些实例。当有活动引用指向它时,ARC是不会释放这个实例的。
为实现这点,当你将类实例赋值给属性、常量或变量时,指向实例的一个强引用(strong reference)将会被构造出来。被称为强引用是因为它稳定地持有这个实例,当这个强引用存在时,实例就不能够被自动释放,因此可以安全地使用。
例子:
[cpp] view plaincopy
class Teacher { var tName : String init(name:String) { tName = name println("老师 \(tName) 实例初始化完成.") } func getName() -> String { return tName } func classing() { println("老师 \(tName) 正在给学生讲课.") } deinit { println("老师 \(tName) 实例析构完成.") } }
测试ARC:
[cpp] view plaincopy
func testArc() { var teacher:Teacher? = Teacher(name:"张三") var refteacher:Teacher? = teacher var refteacher2:Teacher? = teacher<span style="white-space:pre"> </span> <span style="font-family: Arial, Helvetica, sans-serif;"> refteacher = nil teacher?.classing() teacher = nil refteacher2!.classing() refteacher2 = nil refteacher2?.classing() }
输出结果:
[cpp] view plaincopy
老师 张三 实例初始化完成. 老师 张三 正在给学生讲课. 老师 张三 正在给学生讲课. 老师 张三 实例析构完成.
从上面的例子来看,确实swift给我们自动管理了内存,很多时侯开发者都不需要考虑太多的内存管理。但真的是这样吗?真的安全吗?作为开发者要如何用好ARC?
尽管ARC减少了很多内存管理工作,但ARC并不是绝对安全的。下面来看一下循环强引用导至的内存泄漏。
例子:
[cpp] view plaincopy
class Teacher { var tName : String var student : Student? init(name:String) { tName = name println("老师 \(tName) 实例初始化完成.") } func getName() -> String { return tName } func classing() { println("老师 \(tName) 正在给学生 \(student?.getName()) 讲课.") } deinit { println("老师 \(tName) 实例析构完成.") } } class Student { var tName : String var teacher : Teacher? init(name:String) { tName = name println("学生 \(tName) 实例初始化完成.") } func getName() -> String { return tName } func listening() { println("学生 \(tName) 正在听 \(teacher?.getName()) 老师讲的课") } deinit { println("学生 \(tName) 实例析构化完成.") } }
测试泄漏:
[cpp] view plaincopy
func testMemoryLeak() { var teacher :Teacher? var student :Student? teacher = Teacher(name:"陈峰") student = Student(name:"徐鸽") teacher!.student = student student!.teacher = teacher teacher!.classing() student!.listening() teacher = nil student = nil<span style="white-space:pre"> </span> println("释放后输出") teacher?.classing()<span style="white-space:pre"> </span> student?.listening() }
输出结果:
[cpp] view plaincopy
老师 陈峰 实例初始化完成. 学生 徐鸽 实例初始化完成. 老师 陈峰 正在给学生 徐鸽 讲课. 学生 徐鸽 正在听 陈峰 老师讲的课 释放后输出
自始至终都没有调用deinit。因此就会泄漏,此时已经不能采取任何措拖来释放这两个对象了,只有等APP的生命周期结束
实例之间的相互引用,在日常开发中是很常见的一种,哪么如何避免这种循环强引用导致的内存泄漏呢?
可以通过在类之间定义为弱引用(weak)或无宿主引用的(unowned)变量可以解决强引用循环这个问题
弱引用方式:
弱引用并不保持对所指对象的强烈持有,因此并不阻止ARC对引用实例的回收。这个特性保证了引用不成为强引用循环的一部分。指明引用为弱引用是在生命属性或变量时在其前面加上关键字weak。
注意
弱引用必须声明为变量,指明它们的值在运行期可以改变。弱引用不能被声明为常量。
因为弱引用可以不含有值,所以必须声明弱引用为可选类型。因为可选类型使得Swift中的不含有值成为可能。
因此只需要将上述的例子任意一个实例变量前加上weak关键词即可,如:
[cpp] view plaincopy
weak var student : Student? weak var teacher : Teacher?
下面来测试一下weak var student : Student?设为弱引用后,测试释放的时间点(情况一)
[cpp] view plaincopy
var teacher :Teacher? var student :Student? teacher = Teacher(name:"陈峰") student = Student(name:"徐鸽") teacher!.student = student student!.teacher = teacher teacher!.classing() student!.listening() teacher = nil println("释放后输出") teacher?.classing()<span style="white-space:pre"> </span> student?.listening()
经测试输出:
[cpp] view plaincopy
老师 陈峰 实例初始化完成. 学生 徐鸽 实例初始化完成. 老师 陈峰 正在给学生 徐鸽 讲课. 学生 徐鸽 正在听 陈峰 老师讲的课 释放后输出 学生 徐鸽 正在听 陈峰 老师讲的课 学生 徐鸽 实例析构化完成. 老师 陈峰 实例析构完成.
如果 weak var teacher : Teacher?
再来进行测试:(情况二)
[cpp] view plaincopy
var teacher :Teacher? var student :Student? teacher = Teacher(name:"陈峰") student = Student(name:"徐鸽") teacher!.student = student student!.teacher = teacher teacher!.classing() student!.listening() teacher = nil println("释放后输出") teacher?.classing() student?.listening()
输出结果:
[cpp] view plaincopy
老师 陈峰 实例初始化完成. 学生 徐鸽 实例初始化完成. 老师 陈峰 正在给学生 徐鸽 讲课. 学生 徐鸽 正在听 陈峰 老师讲的课 老师 陈峰 实例析构完成. 释放后输出 学生 徐鸽 正在听 nil 老师讲的课 学生 徐鸽 实例析构化完成.
经测试得出结论:
当A类中包函有B类的弱引用的实例,同时,B类中存在A的强引用实例时,如果A释放,也不会影响B的析放,但A的内存回收要等B的实例释放后才可以回收。(情况一的结果)
当A类中包函有B类的强引用的实例时,如果A释放,则不会影响B的析放。(情况二的结果)
无宿主引用方式:
和弱引用一样,无宿主引用也并不持有实例的强引用。但和弱引用不同的是,无宿主引用通常都有一个值。因此,无宿主引用并不定义成可选类型。指明为无宿主引用是在属性或变量声明的时候在之前加上关键字unowned。
因为无宿主引用为非可选类型,所以每当使用无宿主引用时不必使用?。无宿主引用通常可以直接访问。但是当无宿主引用所指实例被释放时,ARC并不能将引用值设置为nil,因为非可选类型不能设置为nil。
注意
在无宿主引用指向实例被释放后,如果你想访问这个无宿主引用,将会触发一个运行期错误(仅当能够确认一个引用一直指向一个实例时才使用无宿主引用)。在Swift中这种情况也会造成应用程序的崩溃,会有一些不可预知的行为发生。因此使用时需要特别小心。
将前面例子改为无宿主引用:
[cpp] view plaincopy
class Teacher { var tName : String var student : Student? init(name:String) { tName = name println("老师 \(tName) 实例初始化完成.") } func getName() -> String { return tName } func classing() { println("老师 \(tName) 正在给学生 \(student?.getName()) 讲课.") } deinit { println("老师 \(tName) 实例析构完成.") } } class Student { var tName : String unowned var teacher : Teacher init(name:String,tcher :Teacher) { tName = name teacher = tcher println("学生 \(tName) 实例初始化完成.") } func getName() -> String { return tName } func listening() { println("学生 \(tName) 正在听 \(teacher.getName()) 老师讲的课") } deinit { println("学生 \(tName) 实例析构化完成.") } }
测试无宿主引用:
[cpp] view plaincopy
func testNotOwner() { var teacher :Teacher? teacher = Teacher(name:"陈峰") var student = Student(name: "徐鸽",tcher: teacher!) teacher!.student = student student.teacher = teacher! teacher!.classing() student.listening() teacher = nil println("老师对象释放后") teacher?.classing() student.listening() }
输出结果:
[cpp] view plaincopy
老师 陈峰 实例初始化完成. 学生 徐鸽 实例初始化完成. 老师 陈峰 正在给学生 徐鸽 讲课. 学生 徐鸽 正在听 陈峰 老师讲的课 老师 陈峰 实例析构完成. 老师对象释放后 Program ended with exit code: 9(lldb)
所以使用无宿主引用时,就需要特别小心,小心别人释放时,顺带释放了强引用对象,所以要想别人释放时不影响到原实例,可以使用弱引用这样就算nil,也不会影响。
上面介绍了,当某个类中的实例对象如果在整个生命周期中,有某个时间可能会被设为nil的实例,使用弱引用,如果整个生命周期中某一实例,一旦构造,过程中不可能再设为nil的实例变量,通常使用无宿主引用。但时有些时侯,在两个类中的相互引用属性都一直有值,并且都不可以被设置为nil。这种情况下,通常设置一个类的实例为无宿主属性,而另一个类中的实例变量设为的隐式装箱可选属性(即!号属性)
如下面的例子,每位父亲都有孩子(没孩子能叫父亲么?),每个孩子都有一个亲生父亲
[cpp] view plaincopy
class Father { let children : Children! let fathername : String init(name:String,childName:String) { self.fathername = name self.children = Children(name: childName,fat:self) } deinit { println("father deinited.") } } class Children { unowned let father : Father let name : String init(name:String ,fat : Father) { self.name = name self.father = fat } deinit { println("children deinited.") } }
测试代码:
[cpp] view plaincopy
var fa = Father(name: "王五",childName: "王八") println("\(fa.fathername) 有个小孩叫 \(fa.children.name)")
输出结果:
[cpp] view plaincopy
王五 有个小孩叫 王八 father deinited. children deinited.
同样可以看到,尽管是循环引用,但还是能正常回收。
另外,还有一种情况,当自身的闭包对自身(self) 的强引用,也会导致内存泄漏。
例子:
[cpp] view plaincopy
class CpuFactory { let cpuName : String let cpuRate : Double init(cpuName:String,rate:Double) { self.cpuName = cpuName self.cpuRate = rate } @lazy var someClosure: (Int, String) -> String = { [unowned self] (index: Int, stringToProcess: String) -> String in return "A \(self.cpuName)" } @lazy var machining: () -> String = { [unowned self] in if self.cpuRate > 10 { return "\(self.cpuName) i7 2.5G" } else { return "\(self.cpuName) i3 2.0G" } } @lazy var machining2 : (CpuFactory) -> String = { [unowned self] (cpu:CpuFactory) -> String in if cpu.cpuRate > 10 { return "\(cpu.cpuName) i7 2.5G" } else { return "\(cpu.cpuName) i3 2.0G" } } deinit { println("Cpu Factroy is deinited.") } }
在这个例子中有三个闭包,分别是带参,和不带参,对于带参的 不能省略[unowned self] (paramers) in操作。否则会编译不过,另外,书中没有提到的,只有声明为@lazy的闭包中才可以使用[unowned self] 否则在普通闭包中使用也会报错。还有一点书中讲到当自身闭包中使用self.时会产生强引用,导至内存泄漏,因此加上[unowned self ] in 这句可以破坏这种强引用,从而使内存得到释放,但经本人亲自验证,就算加上了也没有释放。
测试:
[cpp] view plaincopy
func testClosure() { var cpu : CpuFactory? = CpuFactory(cpuName: "Core",rate: 5) println(cpu!.machining2(cpu!)) cpu = nil }
分别单独验证各句输出结果:
[cpp] view plaincopy
func testClosure() { var cpu : CpuFactory? = CpuFactory(cpuName: "Core",rate: 5) println(cpu!.machining()) cpu = nil }
输出:
[cpp] view plaincopy
Core i3 2.0G
显然cpu = nil也不会释放内存。
再来看第二个。
[cpp] view plaincopy
func testClosure() { var cpu : CpuFactory? = CpuFactory(cpuName: "Core",rate: 5) println(cpu!.machining2(cpu!)) cpu = nil }
输出
[cpp] view plaincopy
Core i3 2.0G Cpu Factroy is deinited.
可见使用自身作为参数传参时,可以释放内存。
同样再测试第三种:
[cpp] view plaincopy
func testClosure() { var cpu : CpuFactory? = CpuFactory(cpuName: "Core",rate: 5) println(cpu!.someClosure(3,"hello")) cpu = nil }
输出
[cpp] view plaincopy
A Core
其实第三和第一种是一样的,都是引用了self.但第一种可以把[unowned self ]in 句注释和不注释的情况下进行测试,可以发现结果是一样的,并没有释放内存。
实在令人有点费解。。。。。。
总结
以上是生活随笔为你收集整理的初探swift语言的学习笔记六(ARC-自动引用计数,内存管理)的全部内容,希望文章能够帮你解决所遇到的问题。
如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。