Swift 内存管理

2,769 阅读6分钟
原文链接: yiqizhongchuang.cn

内存管理

首先针对Swift来说内存管理并没有OC那么多说道,Swift只有ARC一种方式

内容结构

  1. 析构方法
  2. 析构方法的自动继承
  3. Swift语言的内存管理分析
  4. 弱引用
  5. 循环强引用
  6. unowned 解决循环强引用

详细内容

1.析构方法

对象的内存被回收前会被隐式调用,主要用来执行一些额外的操作,比如:关闭文件,断开网络等释放对象持有的一些资源等。等同于oc中的-(void)dealloc方法一样。在对象被销毁的时候被隐式调用。

语法是

deinit{ 代码块 }

代码演示

class FileHandler {
    var fd : Int32? //文件描述符
    init(path :String) {
        let ret = open(path, O_RDONLY)
        if ret == -1 {
            fd = nil
        }else{
            fd = ret
        }
    }
   <!--析构方法-->
    deinit {
        if let oft = fd {
            close(oft)
        }
        print("对象被销毁")
    }
}
<!--对象何时被销毁,对象没有任何引用的时候,只要是Mac路径肯定会有“/etc/passwd”-->
var file : FileHandler? = FileHandler(path : "/etc/passwd”)
<!--如果没有将file指向nil那么对象将不会被销毁-->
file = nil

打印信息为

对象被销毁

2.析构方法的自动继承 - 父类的析构方法会被自动调用,不需要子类管理

代码演示

class Father {
    deinit {
        print("fatherClass deinit")
    }
}

class SonClass : Father {
    deinit {
        print("sonClass deinit")
    }
}

var sone : SonClass?  = SonClass()
<!--当sone变量指向nil之后,对象会被回收,自动调用deinit方法-->
sone = nil

打印信息

sonClass deinit     
fatherClass deinit

3.Swift语言的内存管理分析

  1. 内存管理的对象 - 被管理的是引用类型的对象(class类型),值类型是不需要管理的
  2. 内存管理的原则:当没有任何引用指向某个对象的时候,系统会自动销毁该对象
  3. 如何做到的原理:ARC

代码演示

class MemArr {
    deinit {
        print("deinit")
    }
}

var t0 = MemArr()
var t1 = t0
var t2 = MemArr()
<!--t0指向t2,t1也指向t2,那么t1原来的指向对象会被回收-->
t0 = t2
t1 = t2

打印信息

deinit

4.弱引用

  1. weak字符串修饰的即为弱引用对象,并不会引起引用计数增加,当对象被释放(回收)之后,weak修饰的变量将指向nil,weak引用是一种非常安全的引用方式。ps:weak修饰的类型一定是个可选值类型
  2. unowned字符串修饰的也为弱引用对象,但是与weak不同的是,unowned不允许设置为可选值类型,也不允许nil值的情况。之前的Swift版本,unowned修饰的对象可以正常书写,只有在运行时才会崩溃报错,在3.0版本编译器会检测其修饰的对象是否为nil

代码演示

1. weak使用
class Ref {
    deinit {
        print("ref deinit")
    }

    func test()  {
        print("test")
    }
}

<!--强引用,引用计数加一-->
var ref = Ref()

<!--弱引用-->
weak var weafRef = Ref()
<!--下面的代码与上面的等效,上面的缺省隐式类型-->
<!--`weak var weafRef1 : Ref? = Ref()` 
此处做的类型定义为可选值类型-->
<!--这里采用隐式解包,
因为在使用weak修饰的对象可能返回一个nil,
所以类型会是缺省类型-->
if let red = weafRef {
 <!--因为对象已经被回收所以方法不会被执行-->
    red.test()
}
2. unowned的使用
class Ref {
    deinit {
        print("ref deinit")
    }

    func test()  {
        print("test")
    }
}

<!--会报错,信息为:”Execution was interrupted, reason : EXC_BREAKPOINT *****“-->
unowned var unowned :Ref = Ref()

unowned.test()

打印信息

1. weak的使用
ref deinit
2. unowned的使用
<!--没有打印信息-->

5. 循环强引用

ARC不是万能的,虽然他可以很好的解决内存过早释放的问题,但是在某些场合下不能很好的解决内存泄漏的问题

代码演示问题

class Person {
    let name  : String
    init(name : String) {
        self.name = name
    }
     var apartment : Apartment?
    deinit {
        print("\(name) ")
    }
}

class Apartment {
    let number : Int
    init(number : Int) {
        self.number = number;
    }
    var tenant : Person?
    deinit {
        print("apartment \(number) is being deinit")
    }
}

var john : Person?
var number73 :Apartment?
john = Person(name: "john")
number73 = Apartment(number: 73)
john!.apartment = number73
number73!.tenant = john

//指向了nil,所以变量指向的对象已经被回收,但是都是强引用,
//互相的强引用导致产生了内存泄漏
john = nil
number73 = nil

想要的是将对象都释放掉,并且有打印信息,可是打印信息并没有,原因通过图片演示

屏幕快照 2017-01-16 下午7.50.36.png

john持有Person类的强引用,number73持有Apartment类的强引用,然后在Person类中有属性apartmrnt又指向number73,而Apartmrnt类中也有属性tenant指向john,这就造成了对象之间的互相引用,当john跟number73都指向了nil之后,john与number73都释放了对对象的持有关系,但是Person类与Apartment类却又有互相之间的持有强引用,如下图所示

屏幕快照 2017-01-16 下午7.50.02.png

这就造成了循环引用,导致内存泄漏,虽然对象之间还互相持有强引用,但是原本持有对象强引用的john与number73,已经释放了对对象的引用,简言之,我们无法通过任何东西再访问到Person类和Apartemnt类。

解决办法

将任一对象中的属性用所引用来修饰,使其中一个对象原本的强引用变成弱引用,这样就不会造成循环引用了。

代码演示

class Person {
    let name  : String
    init(name : String) {
        self.name = name
    }
    var apartment : Apartment?
    deinit {
        print("\(name) person is deinit")
    }
}

class Apartment {
    let number : Int
    init(number : Int) {
        self.number = number;
    }
    <!--将该属性设置为weak-->
    weak var tenant : Person?
    deinit {
        print("apartment \(number) is being deinit")
    }
}

var john : Person?
var number73 :Apartment?
john = Person(name: "john")
number73 = Apartment(number: 73)
john!.apartment = number73
number73!.tenant = john
john = nil
number73 = nil

打印信息

john person is deinit
apartment 73 is being deinit

通过打印信息可知,对象已经被回收,互相引用问题已被解决

原因如下图

屏幕快照 2017-01-17 上午11.02.00.png

此图表示了改进代码之后的引用关系,两个类对象之间的相互调用不在是强引用。当john指向nil之后,其指向的Person对象也会被回收,如下图

屏幕快照 2017-01-17 上午11.07.37.png

随后number73也指向了nil,其指向的对象Apartment也也会被回收,如下图

屏幕快照 2017-01-17 上午11.07.53.png

6. unowned 解决循环强引用

代码演示

class Customer {
    let name : String
    var  card :CreditCard?
    init(name : String) {
        self.name = name
    }
    deinit {
        print("\(name) is being deinit")
    }
}

class CreditCard {
    let number : UInt64
    let customer : Customer
    init(number : UInt64, customer : Customer) {
        self.number = number;
        self.customer = customer
    }
    deinit {
        print("Card #\(number) is being deinit")
    }
}

var jeson : Customer?
jeson = Customer(name: "jeson")
jeson!.card = CreditCard(number: 123456789, customer: jeson!)
<!--释放持有对象-->
jeson = nil

打印信息

<!--没有打印信息-->

由于两个类中的存储属性都对互相之间有强引用,造成内存泄漏,对象没有被回收

解决办法

将任一类对象的存储属性,使用unowned来修饰,不过unowned并不能修饰可选值类型,所以unowned就会用来修饰非可选值类型的属性。

修改部分
class CreditCard {
    let number : UInt64
    <!--此处unowned只能修饰非可选值类型属性-->
    unowned let customer : Customer
    init(number : UInt64, customer : Customer) {
        self.number = number;
        self.customer = customer
    }
    deinit {
        print("Card #\(number) is being deinit")
    }
}

修改此处之后,上面的代码输出将会如下

打印信息

jeson is being deinit
Card #123456789 is being deinit