首先我们带着几点疑问去看文章:

  • 值类型(结构体、枚举) 和 引用类型 class 在内存中是如何存储的?
  • 值类型和引用类型性能上有什么不同?
  • 如果两者混合,会发生什么?比如struct 包含了 class 对象。
  • 到底该使用哪个呢?

值类型的定义

  • 值类型直接存储到栈中。每个值类型的变量持有自己的数据,之间互不影响。
  • 引用类型通过指针引用数据(存储在堆中),多个变量可指向同一数据,当操作某一变量时会影响其他变量。

值类型涉及到值拷贝,引用类型涉及到内存分配,引用计数。接下来我们大致了解一下内存段。

内存段

内存是一个很长的字节列表。字节被有序排列,每个字节有它自己的地址。地址空间逻辑上分为4段。Text,Data,Heap, Stack。

  • 文本段包含机器指令。由编译器将Swift代码编译成机器码。文本段只读。
  • 数据段用来存储Swift静态变量,常量,元数据。程序启动时,公共数据初始化值将存储在这个位置。
  • 栈段粗才能临时数据。方法参数和本地变量。当我们调用方法时,将在栈区域开辟一段内存直到方法调用结束这段内存将释放。以及一些值类型数据将存储在Stack。
  • 堆存储所有引用对象,以及一些“假”的值对象(定义的结构体里包含class对象)。

值类型装箱

  1. 当遵循某协议时
    1
    2
    protocol Bar {}
    struct Baz: Bar {}
  2. 当和引用类型混合时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class A {

    }

    struct B {
    let a = A()
    }

    struct C { }
    class D {
    let c = C()
    }
    //结构体B,C 都分配在堆内存中。
  3. 使用泛型
    1
    2
    3
    4
    5
    6
    7
    struct Bas<T> {
    var x: T

    init(xx: T) {
    x = xx
    }
    }
  4. Swift的闭包模型是所有局部变量都被引用。
  5. Inout 参数
    1
    2
    3
    func foo(x: inout Int) {
    x += 1
    }

    拷贝成本

    私有类型如整形,浮点型拷贝时不需要花费RAM内存,一些扩展类型如字符串,数组,set集合,字典等拷贝时需要进行复制。而引用类型由于不直接存储数据,拷贝时候多数是指针拷贝,而不是真正地内容拷贝。如果一个结构体包含引用类型时,而打破了值语义,那么如果要维持值语义,拷贝时候需要针对该对象是否引用计数为1的情况下,重新创建一份对象进行赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Ref {}

struct MyStruct {
let ref1 = Ref()
}

class MyClass {
let ref1 = Ref()
}

let a = MyStruct()
let anotherA = a
print("struct self:", CFGetRetainCount(a as CFTypeRef)) //1
print("struct ref1:", CFGetRetainCount(a.ref1)) //3

let b = MyClass()
let anotherB = b
print("class self:", CFGetRetainCount(b)) //3
print("class ref1:", CFGetRetainCount(b.ref1)) //2

总结:

  • 值类型内部包含引用类型,打破了值语义,结构体将存储在堆上。赋值时将带来引用计数更大花销,引用对象可能被多个地方引用。实际业务可能受到影响。
  • 具有动态行为的值类型(如数组和字符串)应采用写时复制来分摊复制成本。写复制可重写get方法,根据是否引用唯一计数来决定是直接引用还是复制一份。
  • 当使用泛型或者协议时的值类型将发生装箱操作给内存带来更大花销。

写复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Foo: NSObject {
var bar: String = "bar"
}

struct FooHolder {
private var _foo: Foo = Foo()

var foo: Foo {
mutating get {
if isKnownUniquelyReferenced(&_foo) {
return _foo
} else {
let newFoo = Foo()
newFoo.bar = _foo.bar
_foo = newFoo
return _foo
}
} set {
_foo = newValue
}
}

var intValue: Int = 1
}

var fooHolder = FooHolder()
var fooHolder2 = fooHolder

fooHolder2.foo.bar = "bar2"
fooHolder2.intValue = 2

print(fooHolder.intValue)
// 1
print(fooHolder2.intValue)
// 2

print(fooHolder.foo.bar)
// bar
print(fooHolder2.foo.bar)
// bar2

评论