该内容已被发布者删除 该内容被自由微信恢复
文章于 2020年2月26日 被检测为删除。
查看原文
被用户删除
其他

Swift 内存管理(一)

2017-02-04 吴昕阳 一起众创

概念简介

        首先来介绍什么是内存管理,内存管理是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效快速的分配,并且在适当的时候释放和回收内存资源。内存管理这一概念在iOS开发与安卓开发中都尤为的重要。

  在iOS开发中内存管理主要管理的方式根据语言的不同有所区别,先说Objective-C语言,内存管理方式分为:MRC,ARC两种方式。内存管理的原理引用计数

  这里简单介绍一下引用计数的概念:苹果公司在设计语言的时候,在对象内部设置了一个计数器,当使用new,alloc等关键字创建一个对象时,系统会为这个对象开辟内存空间,这个计数器会自动加一。当引用计数为零的时候系统会回收这个对象所占用的内存。

  MRC,是手动内存管理。也就是说,主动创建的对象,要手动释放对象的引用计数,即release。使用release方法会使对象的引用计数减一。

  ARC,是自动内存管理,也就是主动创建的对象,系统会自动检测对象是否还有引用指向这个对象,如果没有,那么系统会回收这个对象。

  针对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

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

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

这就造成了循环引用,导致内存泄漏,虽然对象之间还互相持有强引用,但是原本持有对象强引用的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(number73)

john!.apartment = number73

number73!.tenant = john

john = nil

number73 = nil

打印信息

john person is deinit

apartment 73 is being deinit

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

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

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

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(number123456789customer: 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


扫描二维码

关注更多精彩


点击“阅读原文”


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存