众所周知Swift中Array是值类型,如果其中元素为值类型和引用类型,分别会发生什么?
相关验证方法
检查不同层次的地址
swift
// 1. 栈变量地址
withUnsafePointer(to: &array) {
print("\(name) 栈地址: \($0)")
}
// 2. 堆缓冲区地址
array.withUnsafeBufferPointer {
print("数组缓冲区地址: \(String(describing: $0.baseAddress))")
}
// 3. 元素地址(引用类型时比较)
if let first = array.first as? AnyObject {
print("\(name)[0] 对象地址: \(ObjectIdentifier(first))")
}
}
元素为引用类型
随便定义一个类,并创建列表1,然后直接赋值给列表2
swift
class Person {
var name: String
init(name: String) { self.name = name }
}
var people1 = [Person(name: "Alice"),
Person(name: "Bob")]
var people2 = people1
用withUnsafePointer打印此时两个数组的栈地址(指向数组的指针)
swift
withUnsafePointer(to: &people1) { ptr in
print("people1 地址: \(ptr)")
}
withUnsafePointer(to: &people2) { ptr in
print("people2 地址: \(ptr)")
}
// 输出结果
// people1 地址: 0x000000010df001a0
// people2 地址: 0x000000010df001a8
确实是两个不同的数组指针(废话!),但是我们再通过withUnsafeBufferPointer获取数组缓冲区地址
swift
people1.withUnsafeBufferPointer { buffer in
if let baseAddress = buffer.baseAddress {
print("people1缓冲区地址(堆): \(baseAddress)")
}
}
people2.withUnsafeBufferPointer { buffer in
if let baseAddress = buffer.baseAddress {
print("people2缓冲区地址(堆): \(baseAddress)")
}
}
// 输出结果
// people1缓冲区地址(堆): 0x000000014d2040c0
// people2缓冲区地址(堆): 0x000000014d2040c0
会发现指向的是同一块缓冲区
如果我们更改people2中元素的name,指针地址和缓冲区地址都没有任何变化(这里就不贴代码和打印结果了),但是如果新增元素
swift
people2.append(Person(name: "newPerson"))
withUnsafePointer(to: &people2) { ptr in
print("people2 地址: \(ptr)")
}
people2.withUnsafeBufferPointer { buffer in
if let baseAddress = buffer.baseAddress {
print("people2缓冲区地址(堆):\(baseAddress)")
}
}
// 输出结果:
// people2 地址: 0x000000010df001a8
// people2缓冲区地址(堆): 0x000000014f404b10
指针地址没变,但是缓冲区地址变了!证明Swift中的数组是写时复制,新开辟了缓冲区。(删除同理)
但是缓冲区里存的是什么?打印下数组中的元素看看
swift
/* people1
people1 元素对象地址:
[0]: 0x122b04570
[1]: 0x122b04590
people2 元素对象地址:
[0]: 0x122b04570
[1]: 0x122b04590
[2]: 0x122b05ea0
*/
得出结论。虽然缓冲区确实开了新的,但是引用类型的元素还是不会被复制,相当于只是开了一块新地址存引用类型元素的指针而已。
结论:
- Array是值类型
- 赋值副本Array时发生逻辑复制(新的数组指针 在栈上),修改副本中的元素也会更改到原Array中的元素
- 修复副本Array时才实际复制堆缓冲区
元素为值类型
如果真的能读到值类型,相信也能看懂直接用代码解释了
swift
var array1 = ["AAA", "BBB", "CCC"]
var array2 = array1
// 输出结果:
// 栈地址验证,不同
// array1 栈地址: 0x00000001101d0058
// array2 栈地址: 0x00000001101d0060
// 缓冲区 暂时相同
// array1 缓冲区地址: 0x0000000129b04440
// array2 缓冲区地址: 0x0000000129b04440
此时修改元素再查看,array2已经开辟新的缓冲区,就不重复贴新增和删除的代码了,结果也是如此。
swift
array2[0] = "new AAA"
// 输出结果:
// array1 缓冲区地址: 0x0000000129b04440
// array2 缓冲区地址: 0x0000000129b0d950
但是!修改了array2并没有像array1那样影响到同一个元素,现在用下面的方法验证下数组中的元素,打印修改后的结果
swift
array1.withUnsafeBufferPointer { buffer in
if let baseAddress = buffer.baseAddress {
for i in 0..<buffer.count {
let elementAddress = baseAddress + i
print("array[\(i)] 地址: \(elementAddress), 值: \(elementAddress.pointee)")
}
}
}
array2.withUnsafeBufferPointer { buffer in
if let baseAddress = buffer.baseAddress {
for i in 0..<buffer.count {
let elementAddress = baseAddress + i
print("array[\(i)] 地址: \(elementAddress), 值: \(elementAddress.pointee)")
}
}
}
/* 输出结果:
array[0] 地址: 0x0000000127504170, 值: AAA
array[1] 地址: 0x0000000127504180, 值: BBB
array[2] 地址: 0x0000000127504190, 值: CCC
array[0] 地址: 0x000000012750ba70, 值: newAAA
array[1] 地址: 0x000000012750ba80, 值: BBB
array[2] 地址: 0x000000012750ba90, 值: CCC
*/
元素地址不同,值也不同
小总结
| 元素类型 | 值类型 | 引用类型 |
|---|---|---|
| 赋值 | 逻辑复制 | 逻辑复制 |
| 缓冲区共享 | 初始共享 | 初始共享 |
| 元素独立性 | 完全独立 | 共享对象 |
| 写时复制触发 | 修改时 | 修改结构时候(增删) |
| 内存影响 | 元素复制 | 只复制指针 |