内存管理
跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间)
Swift的ARC中有3中引用:
- 强引用(strong reference)
- 默认情况下,引用都是强引用
- 弱引用(weak reference)
- 必须是可选类型的var,因为实力销毁后,ARC会自动将弱引用设置为nil
- ARC自动给弱引用设置nil时,不会触发属性观察器
- 无主引用(unowned reference)
- 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似OC中的unsafe_unretained)
- 试图在实例销毁后访问无主访问,会产生运行时错误(野指针)
- Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated
weak、unowned的使用限制
-
weak 、unowned只能用在类实例上面
如果Livable后无AnyObject,p2则会报错
Autoreleasepool(自动释放池)
Autoreleasepool函数
autoreleasepool {
class Person {
}
var p = Person()
}
循环引用(Reference Cycle)
- weak、unowned 都能解决循环引用的问题,unowned 要比weak少一些性能消耗
- 如果在生命周期中,随时可能变为nil,使用weak
- 初始化复制后再也不会变为nil,使用unowned
看下面的代码:
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var person:Person? = Person(name: "John")
var apart:Apartment? = Apartment(unit: "4A")
//相互强持有
person!.apartment = unit4A
apart!.tenant = John
在上面代码中,Person 实例现在有了一个指向 Apartment 实例的强引用,而 Apartment 实例也有了一个指向 Person 实例的强引用。当你把这两个变量设为 nil 时,没有任何一个反初始化器被调用。循环强引用会一直阻止 Person 和 Apartment 类实例的释放,这就在你的应用程序中造成了内存泄漏
循环引用示例
如何解决呢?
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
使用weak破除循环引用
再看下面的代码:
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer? = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: John!)
现在 Customer 实例对 CreditCard 实例有一个强引用,并且 CreditCard 实例对 Customer 实例有一个无主引用。由于 Customer 的无主引用,当你断开 john 变量持有的强引用时,那么就再也没有指向 Customer 实例的强引用了。
因为不再有 Customer 的强引用,该实例被释放了。其后,再也没有指向 CreditCard 实例的强引用,该实例也随之被释放了
使用unowmed破除循环引用
更详细讲解可以查看Swift-自动引用计数
闭包的循环引用
- 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
- 如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的(因为实例初始化完毕后才能引用self)
- 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的声明周期就结束了)
//示例1:
//产生循环引用,导致Person对象无法释放(看不到deinit被调用)
class Person{
var fn:(() -> ())?
func run() {
print("run")
}
deinit {
print("Person deinit")
}
}
func test() {
let p = Person()
p.fn = {
p.run()
}
}
test()
如何解决上面的问题? 答:在闭包表达式的捕获列表声明weak或者unowned引用,解决循环引用问题
p.fn = {
[weak p] in
//因为声明weak 所以p变为可选类型
p?.run()//可选链
}
//或者
p.fn = {
[unowned p] in
p.run()
}
//示例2:
//fn内部用到了实例成员(属性、方法)必须强制要求使用self
class Person{
lazy var fn:(() -> ()) = {
[weak weakself = self] in//使用unowned也可以
weakself?.run()
}
func run() {
print("run")
}
deinit {
print("Person deinit")
}
}
func test(){
var p = Person()
p.fn()
}
test()
//示例3:
class Person {
var age = 0
lazy var getAge: Int = {//一声明就调用 马上结束
self.age//此处也可以不用self 因为系统判定此处不会循环引用
}()
//此为闭包表达式调用
//var fn = {
// 10
//}
//var age: Int = fn()
deinit {
print("Person deinit")
}
}
var per = Person()
@escaping(逃逸闭包)
- 逃逸闭包、非逃逸闭包,一般当做参数传递给函数
- 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
- 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明
逃逸闭包、非逃逸闭包
import Dispatch
typealias Fn = () -> ()
class Person {
var fn:Fn
//fn是逃逸闭包
init(fn:@escaping Fn) {
self.fn = fn
}
func run() {
//DispatchQueue.global().async也是一个逃逸闭包
//它用到了实例成员(属性、方法) 编译器也会强制要求明确写出self
DispatchQueue.global().async {
self.fn()
}
}
}
- 逃逸闭包不可以捕获inout参数
typealias Fn = () -> ()
func other1(_ fn: Fn) {
fn()
}
func other2(_ fn: @escaping Fn) {
fn()
}
func test(value: inout Int) -> Fn{
other1 {
value += 1
}
other2{//逃逸闭包 有可能函数已经执行完毕
value += 1//Escaping closure captures 'inout' parameter 'value'
}
func plus(){//同样为逃逸闭包 不可以
value += 1
}
return plus//Escaping closure captures 'inout' parameter 'value'
}
func abc() {
var num = 10
test(value: &num)
}