Swift学习之内存管理

版权声明:不积跬步无以至千里,不积小流无以成江海! https://blog.csdn.net/Bolted_snail/article/details/88355563

简介

  • Java和C#为代表的内存垃圾回收机制(Garbage Collection),程序员不用关心内存释放的问题。这种方式在后台有一个线程负责检查已经不再使用的对象,然后将其释放。由于后台有一个线程一直运行,这种方式会严重影响性能。
  • Swift则采用自动引用计数(ARC)来管理引用类型的内存,及对类所创建的对象采用ARC管理;而值类型(整型、浮点型、布尔型、字符串型、集合、元组,枚举、结构体等)是由处理器自动管理的,程序员不需要管理它们的内存。(引用类型内存区域都是在堆上,需要人为管理;值类型内存分配区域在栈上,由处理器管理,不需要人为管理)

引用计数

  • 每个Swift类创建的对象都有一个内部计数器,这个计数器跟踪对象的引用次数,称为引用计数(RC);当对象被创建时,引用计数为1,每次对象呗引用的时候其引用计数加1,当不需要的时候对象引用断开(赋值为nil),其引用计数减1.当引用计数为0时,对象内存被释放。
class Employee {
    deinit {
        print("释放了对象!")
    }
}
var emp1: Employee?
var emp2: Employee?
var emp3: Employee?
emp1 = Employee() //1
emp2 = emp1 //2
emp3 = emp1 //3
emp1 = nil //2
emp2 = nil //1
emp3 = nil //0,释放对象

强引用循环

  • 当两个对象的存储属性互相引用对方的时候,一个对象释放的前提是对方先释放,另一个对象释放的前提也是对方先释放。这样就会导致类似于死锁的状态,最后谁都不能释放,从而导致内存泄露。这种现象就是强引用循环
class Employee {
    var dep: Department?
    deinit {
        print("Employee对象释放了!")
    }
}

class Department {
     var emp: Employee?
    deinit {
        print("Department对象释放了!")
    }
}
var emp: Employee? = Employee() //1
var dep: Department? = Department()//1
emp?.dep = dep //2
dep?.emp = emp//2
emp = nil //1
dep = nil //1

上面的emp和dep对象相互引用,引用计数最终都是1,都不能够释放内存。

打破引用循环

  • Swift提供两种办法来解决强引用循环问题:弱引用(weak reference)无主引用(unowned reference)
  • 弱引用允许循环引用的一个对象不采用强引用方式引用另外一个对象,这样就不会引起强引用循环。弱引用适用于引用对象可以没有值的情况。因为弱引用可以没有值,我们必须将每一个弱引用声明为可选类型。弱引用使用关键字weak声明。
class Employee {
    weak var dep: Department?
    deinit {
        print("Employee对象释放了!")
    }
}
class Department {
     var emp: Employee?
    deinit {
        print("Department对象释放了!")
    }
}
var emp: Employee? = Employee() //1
var dep: Department? = Department()//1
emp?.dep = dep //2
dep?.emp = emp//2
dep = nil //0
emp = nil //0
  • 无主引用与弱引用一样,允许循环引用中的一个对象不采用强引用方式引用另外一个对象,因此不会引起强引用循环。
  • 无主引用适用于引用对象永远有值的情况,它总是被定义为非可选类型。无主引用使用关键字unowned表示。
class Employee {
    var dep: Department?
    deinit {
        print("Employee对象释放了!")
    }
}

class Department {
    unowned var emp: Employee
    init(emp: Employee) {
        self.emp = emp
    }
    deinit {
        print("Department对象释放了!")
    }
}
var emp: Employee? = Employee() //1
var dep: Department? = Department(emp: emp!)//1
emp?.dep = dep //2
dep = nil //0
emp = nil //0

闭包中的强引用循环

  • 闭包的本质是函数类型,所以也是引用类型,因此也可能在闭包和上下文捕获变量(或常量)之间出现强引用循环问题。
  • 并不是所有的捕获变量(或常量)都会发生强引用循环问题。只有将一个闭包赋值给对象的某个属性,并且这个闭包使用的该对象,才会产生闭包强引用循环。
class Employee {
    var firstName: String
    var lastName: String
    init(firstName: String,lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    lazy var fullName:() -> String = {
        return self.firstName+"."+self.lastName
    }
    deinit {
        print("Employee对象释放了!")
    }
}
var emp: Employee? = Employee(firstName: "Jack", lastName: "Lee")
print(emp!.fullName())
emp = nil

这里虽然emp置nil了,但是因为fullName是个闭包属性,并且引用了self,造成了闭包强循环引用,此时并不能将emp释放。

  • 解决闭包强引用循环有两种方法:弱引用和无主引用。到底应该采用弱引用还是无主引用,与两个对象之间的选择条件是:捕获的对象是否可以为nil。
  • 如果闭包和捕获的对象总是互相引用并且总是同时销毁,则将闭包内的捕获声明为无主引用。当捕获对象有时可以为nil时,则将闭包内的捕获声明为弱引用。若果捕获的对象绝对不会为nil,那么应该采用无主引用
  • Swift在闭包中定义了捕获列表来解决强引用循环问题,基本语法如下:
lazy var 闭包:<闭包参数列表> -> <返回值类型> = {
     [unowned 捕获对象] <闭包参数列表> -> <返回值类型> in
   或 [weak 捕获对象] <闭包参数列表> -> <返回值类型> in
    // 闭包体
    }

如果是无参数情况下的捕获列表,可以省略参数列表,只保留in,Swift编译器会通过上下文推断出参数列表和返回值类型,可以加下如下:

 lazy var 闭包:<闭包参数列表> -> <返回值类型> = {
     [unowned 捕获对象] in
   或 [weak 捕获对象] in
    // 闭包体
    }
class Employee {
    var firstName: String
    var lastName: String
    init(firstName: String,lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    lazy var fullName:() -> String = {
        [unowned self] in
        return self.firstName+"."+self.lastName
    }
    deinit {
        print("Employee对象释放了!")
    }
}
var emp: Employee? = Employee(firstName: "Jack", lastName: "Lee")
print(emp!.fullName())
emp = nil
class Employee {
    var firstName: String
    var lastName: String
    init(firstName: String,lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    lazy var fullName:() -> String = {
        [weak self] in
        return self!.firstName+"."+self!.lastName
    }
    deinit {
        print("Employee对象释放了!")
    }
}
var emp: Employee? = Employee(firstName: "Jack", lastName: "Lee")
print(emp!.fullName())
emp = nil

可以看出用unowned修饰的self不需要解包,因为self是明确的必须有值;用weak修饰的self,必须要self!来强制解包,因为self必须是可选类型,如果使用必须强制解包。

猜你喜欢

转载自blog.csdn.net/Bolted_snail/article/details/88355563