当生命周期结束会调用deinit()函数进行释放内存空

作者: 编程  发布:2019-11-22

在swift中,每一个对象都有生命周期,当生命周期结束会调用deinit()函数进行释放内存空间。

之前我在CSDN上写过一篇博客:OC内存管理、ARC、property属性、__strong、__weak、__block

 

观察这一段代码:

今天我们来整理一下Swift的内存管理与循环引用的解决问题-weak、unowned:

/*

class Person{

    var name: String
    var pet: Pet?

    init(name: String){
        self.name = name
        print("Person", name, "is initialized")
    }

    init(name: String, petName: String){
        self.name = name
        self.pet = Pet(name: petName)
        print("Person", name, "is initialized")
    }

    deinit{
        print("Person", name, "is deinitialized!")
    }
}

class Pet{

    var name: String
    init(name: String){
        self.name = name;
        print("Pet", name, "is initialized")
    }

    deinit{
        print("Pet", name, "is deinitialized!")
    }
}

内存管理

 Swift内存管理:

这段代码创建了两个类,分别是Person类和Pet类,每个类中都有init方法进行创建对象和deinit方法来释放内存空间,其中Person类中有两个init方法,分别对应着是否包含Pet类的名称。

swift的内存管理也是使用的ARC:当我们初始化创建一个对象实例的时候,swift就会替我们管理和分配内存,此时的引用计数为1,当对其进行init(copy/mutableCopy)时,引用计数会+1,而当实例被销毁时,引用计数就会-1。当系统检测到引用计数为0的时候,就会释放掉这个内存。

 1.管理引用类型的内存, 不会管理值类型, 值类型不需要管理;

当我们调用这两个方法:

但是,这种引用计数会产生一个问题就是循环引用:

 2.内存管理原则: 当没任何强引用指向对象, 系统会自动销毁对象(默认情况下所有的引用都是强引用);

var snow: Person? = Person(name: "snow", petName: "wolf")
snow = nil

循环引用

 3.如果做到该原则: ARC 自动回收内存

两步的执行结果是:

class A {

 */

Pet wolf is initialized
Person snow is initialized
Person snow is deinitialized!
Pet wolf is deinitialized!

var b:B?

 

会发现在创建snow这个对象的时候调用的是第二个init方法,在这个方法中会创建一个新的Pet对象,因此会首先打印出Pet wolf is initialized然后是Person snow is initialized。当对snow对象进行内存释放的时候,将nil赋给这个对象,那么会释放snow这个内存空间,同时也会释放wolf这个内存空间。

init() { print }

class Person {

但是如果我们调用第一种init方法的时候我们会发现:

deinit { print }

    var name:String

var snow: Person? = Person(name: "snow")

var wolf: Pet? = Pet(name: "wolf")
snow?.pet = wolf

snow = nil
wolf = nil

}

    init(name:String) {

我们首先创建了一个snow对象,之后又创建了一个wolf对象,然后将wolf添加到snow对象中去,但是当我们对这snow这个对象进行内存释放的时候会发现:

class B {

        self.name = name

Person snow is initialized
Pet wolf is initialized
Person snow is deinitialized!

var a:A?

    }

仅仅只有snow的内存空间被释放了,但是wolf的内存空间并没有被释放,这里就和swift内存管理中的引用计数有关了:

init() { print }

    deinit {

当我们创建了snow这个对象之后,我们就为它开辟了一个内存空间,命名为a,这时候snow这个对象引用了这片内存空间,这片内存空间的引用计数就是1,

deinit { print }

        print("Person deinit")

同样地当我们创建了wolf这个对象之后,我们就为它开辟了一个内存空间,命名为b,这时候wolf这个对象引用了这片内存空间,这片内存空间的引用计数就是1,

}

    }

当我们将snow?.pet = wolf之后,那么snow中的一个属性也指向了创建wolf这个对象的内存空间,那么这篇内存空间的引用计数就是2.

var a:A?; a = A()

}

当我们对snow = nil进行内存空间的释放,那么内存空间a的引用计数就为0了,同时内存空间b的引用计数就为1了。

var b:B?; b = B()

var p:Person? = Person(name: "xiaohange")

当系统发现一篇内存空间的引用计数为0,那么,系统就会释放这片内存空间,此时内存空间a就被释放了。

a!.b = b; b!.a = a

//p = nil

但是内存空间b的引用计数为1,系统不会进行自动的内存释放。只有当我们进行:

a = nil; b = nil

 

wolf = nil

你会发现,A和B的析构函数deinit都没有调用,因为当a执行析构的时候,b.a还在对其进行引用,当b析构的时候,a.b也在对b进行引用。这时候解决的方法就是对其中的某一个声明进行若引用,即加上weak:

 

操作之后,这片内存空间b才会被释放。

weak var b:B?

/** weak弱引用 **/

同样地对于这样代码:

另外一种造成循环引用的问题就是闭包:闭包中对任何元素的引用都会被闭包自动持有,如果我们在闭包中需要使用self的话,那就相当于闭包对self持有,而block又是被self直接或间接持有,这样就造成了循环引用。例如下面的代码:

class Person2 {

import UIKit

class Person{

    var name: String

    init(name: String){
        self.name = name
        print("Person", name, "is initialized")
    }

    deinit{
        print("Person", name, "is being deinitialized!")
    }
}

var person1: Person? = Person(name: "liuyubobobo")
var person2: Person? = person1
var person3: Person? = person1

class C{

    var name:String

那么person1的内存空间的引用计数为3,如果释放这片内存空间的话,需要将三个对象都为nil

var name:String

    init(name:String) {

如果仅仅是将person1=nil的话,并不会释放这一片内存空间。

lazy var block: = {

        self.name = name

print(self.name )

    }

}

    deinit {

init(name:String) {

        print("Person2 deinit")

self.name = name

    }

print

}

}

//强引用, 引用计数+1

deinit {

var strongP = Person2(name: "hjq") //1

print

var strongP2 = strongP //2

}

 

}

//1.弱引用, 引用计数不变;

var c:C? = C

//2.如果利用weak修饰变量, 当对象释放后会自动将变量设置为nil;

c?.block()

//3.所以利用weak修饰的变量必定是一个可选类型, 因为只有可选类型才能设置为nil.

c = nil

weak var weakP:Person2? = Person2(name: "hjq")

这里C的析构函数也是没有执行的。block是self的属性,block里面又对self持有,这就形成了循环引用。所以这里我们可以使用unowned,也可以使用weak:

if let p = weakP{

//unowned

    print(p)

lazy var block: = {[unowned self] in

}else{

print(self.name)

    print(weakP as Any)

}

}

//weak

 

lazy var block: = {[weak self] in

/*

if let strongSelf = self{

 unowned无主引用, 相当于OC unsafe_unretained

print(strongSelf.name)

 unowned和weak的区别:

}

 1.利用unowned修饰的变量, 对象释放后不会设置为nil, 不安全;

}

   利用weak修饰的变量, 对象释放后会设置为nil;

那么这两个使用有什么区别呢?接下来看一个例子:

 2.利用unowned修饰的变量, 不是可选类型; 利用weak修饰的变量, 是可选类型;

class C{

 什么时候使用weak?

var name:String

 什么时候使用unowned?

lazy var block: = {[unowned self] in

 */

print(self.name)

 

}

class Person3 {

init(name:String) {

    var name:String

9159.com ,self.name = name

    init(name:String) {

print

        self.name = name

}

    }

deinit {

    deinit {

print

        print("Person3 deinit")

}

    }

}

}

class D{

unowned var weakP3:Person3 = Person3(name: "hjq")

var block:)!

 

init(callBack:)?) {

 

self.block = callBack!

/*

print

 循环引用:

}

 ARC不是万能的, 它可以很好的解决内存问题, 但是在某些场合不能很好的解决内存泄露问题;

deinit {

 例如: 两个或者多个对象之间的循环引用问题

print

 */

}

 

}

//例1:

var c:C? = C

class Apartment {

var d = D.init(callBack:c?.block)

    let number:Int      //房间号

c!.block()

    var tenant:Person4? //租客

c = nil

    init(number:Int) {

d.block()

        self.number = number

这里当你运行到 d.block()的时候,是会有一个error的

    }

因为当d.block()执行的时候,c已经被析构掉了,而闭包里的self肯定也是不存在的,是一个nil,这个时候执行的话self.name就会报错。所以在我们不确定是否有外部变量在持有这个block的时候,我们就应该使用weak更为安全,因为使用weak的话self.name需要改成可选性的self?.name,这个时候self?.name肯定就为nil了。所以换成weak之后,在playground里的d.block()就不会有错误了,而且block也是会正常执行的,只不过print(self?.name)打印出来为nil。

    deinit {

欢迎大家访问我的github:

        print("(self.number) deinit")

    }

}

 

class Person4 {

    let name:String

    weak var apartment: Apartment? //公寓

    init(name:String) {

        self.name = name

    }

    deinit {

        print("(self.name) deinit")

    }

}

 

var p4:Person4? = Person4(name: "han")

var a4:Apartment? = Apartment(number: 888)

 

p4!.apartment = a4 //人有一套公寓

a4!.tenant = p4!   //公寓中住着一个人

// 两个对象没有被销毁, 但是我们没有办法访问他们了, 这就出现了内存泄露!

p4 = nil

a4 = nil

 

 

 

//例2:

class CreaditCard {

    let number:Int

    //信用卡必须有所属用户;

    //当某一个变量或常量必须有值, 一直有值, 那么可以使用unowned修饰

    unowned let person:Person5

    init(number:Int, person:Person5) {

        self.number = number

        self.person = person

    }

    deinit {

        print("(self.number) deinit")

    }

}

class Person5 {

    let name:String

    var card:CreaditCard? //人不一定有信用卡

本文由9159.com发布于编程,转载请注明出处:当生命周期结束会调用deinit()函数进行释放内存空

关键词:

上一篇:没有了
下一篇:没有了