Swift Array的写时复制

众所周知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
*/

得出结论。虽然缓冲区确实开了新的,但是引用类型的元素还是不会被复制,相当于只是开了一块新地址存引用类型元素的指针而已。

结论:

  1. Array是值类型
  2. 赋值副本Array时发生逻辑复制(新的数组指针 在栈上),修改副本中的元素也会更改到原Array中的元素
  3. 修复副本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
*/

元素地址不同,值也不同

小总结

元素类型 值类型 引用类型
赋值 逻辑复制 逻辑复制
缓冲区共享 初始共享 初始共享
元素独立性 完全独立 共享对象
写时复制触发 修改时 修改结构时候(增删)
内存影响 元素复制 只复制指针
相关推荐
初级代码游戏1 天前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏1 天前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui2 天前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift
zhangfeng11332 天前
CSDN星图 支持大模型微调 trl axolotl Unsloth 趋动云 LLaMA-Factory Unsloth ms-swift 模型训练
服务器·人工智能·swift
zhyongrui3 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
大熊猫侯佩3 天前
Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学
swift·observable·actor·concurrency·sendable·nonsendable·data race
2501_915921435 天前
在没有源码的前提下,怎么对 Swift 做混淆,IPA 混淆
android·开发语言·ios·小程序·uni-app·iphone·swift
00后程序员张5 天前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
大熊猫侯佩6 天前
星际穿越:SwiftUI 如何让 ForEach 遍历异构数据(Heterogeneous)集合
swiftui·swift·遍历·foreach·any·异构集合·heterogeneous
hjs_deeplearning6 天前
认知篇#15:ms-swift微调中gradient_accumulation_steps和warmup_ratio等参数的意义与设置
开发语言·人工智能·机器学习·swift·vlm