「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」。
前言
- 值类型和引用类型是Swift中的核心概念,了解它们是每位Swift开发人员的基础, 在以后的探索中多次提到值类型和引用类型,所以在这里做个笔记。供以后参考。
内容
-
- 值类型和引用类型的概念
-
- 值类型和引用类型的内存管理
-
- 值类型和引用类型的选择
一 、值类型和引用类型的定义
值类型(Value Type) :即每个实例保持一份数据拷贝。
引用类型(Reference Type) :即所有实例共享一份数据拷贝。
Swift有三种声明类型的方式:class,struct和enum。它们可以分为值类型(struct和enum)和引用类型(class)。它们在内存中的存储方式不同决定它们之间的区别:
- 值类型是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被拷⻉。
- 实际上,Swift 中所有的
基本类型:整数 (integer)、浮点数(floating-point number)、布尔值(boolean)、字符串串(string)、数组 (array)和字典(dictionary),都是值类型,其底层是使用结构体实现的。Swift 中所有的结构体和枚举类型都是值类型。这意味着它们的实例例,以及实例例中所包含的任何 值类型的属性,在代码中传递的时候都会被复制。- 与值类型不同,引⽤类型在被赋予到一个变量量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引⽤,而不是其拷贝。
验证值类型:
import UIKit
struct Point {
var x: Double
var y: Double
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let point1 = Point(x: 3, y: 5)
var point2 = point1
print(point1) // Point(x: 3.0, y: 5.0)
print(point2) // Point(x: 3.0, y: 5.0)
point2.x = 5
print(point1) // Point(x: 3.0, y: 5.0)
print(point2) // Point(x: 3.0, y: 5.0)
}
}
//打印point1 不随着 point2 而变化 。说明他们内存独立
Point(x: 3.0, y: 5.0)
Point(x: 3.0, y: 5.0)
Point(x: 5.0, y: 5.0)
验证引用类型:
import UIKit
class Point {
var x: Double = 0.0
var y: Double = 0.0
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let point1 = Point()
point1.x = 3.0
point1.y = 5.0
let point2 = point1
print(point1)
print(point2)
point2.x = 5
print(point1.x , point1.y)
print(point2.x , point2.y)
}
}
//打印point1 随着 point2 而变化 。说明他们公用一块内存
5.0 5.0
5.0 5.0
二、 值类型和引用类型的内存管理
值类型存储在栈区。每个值类型变量都有其自己的数据副本,并且对一个变量的操作不会影响另一个变量。引用类型存储在其他位置(堆区),我们在内存中有一个指向该位置的引用。引用类型的变量可以指向相同类型的数据。因此,对一个变量进行的操作会影响另一变量所指向的数据
栈区存储临时数据:方法的参数和局部变量。每次我们调用一个方法时,都会在栈上分配一块新的内存。该方法退出时,将释放该内存。除特殊情况(下面会讲),所有Swift值类型都在此处。
堆区存储具有生存期的对象。这些都是Swift引用类型,还有一些值类型的情况。堆和栈朝着彼此增长堆区的分配一般按照地址从小到大进行,而栈区的分配一般按照地址从大到小进行分配。
【堆与栈分配的成本】
栈区内存分配和销毁的工作原理与数据结构中的栈相同。你只能从栈顶压栈或出栈。指向栈顶的指针足以实现这两个操作。因此,栈指针可以腾出空间来分配其他更多的内存。当函数执行完退出时,我们将栈指针增加到调用此方法之前的位置。(为什么增加才能回到调用之前的地址,刚说了栈是从大到小进行分配的)
- 栈分配和释放的成本相当于整数复制的成本
堆分配过程涉及的东西很多。我们必须搜索堆区以找到适合它大小的空内存块。我们还必须同步堆,因为多个线程可能同时在其中分配内存。为了从堆中释放内存,我们必须将该内存重新插入适当的位置。
堆分配和释放的成本比栈要大得多