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
*/

元素地址不同,值也不同

小总结

元素类型 值类型 引用类型
赋值 逻辑复制 逻辑复制
缓冲区共享 初始共享 初始共享
元素独立性 完全独立 共享对象
写时复制触发 修改时 修改结构时候(增删)
内存影响 元素复制 只复制指针
相关推荐
汉秋6 小时前
SwiftUI 中的 compositingGroup():真正含义与渲染原理
swiftui·swift
汉秋7 小时前
SwiftUI 中的 @ViewBuilder 全面解析
swiftui·swift
胖虎121 小时前
SwiftUI 页面作为一级页面数据被重置问题分析
ios·swiftui·swift·state·observedobject·stateobject·swiftui页面生命周期
健了个平_241 天前
【iOS】如何在 iOS 26 的UITabBarController中使用自定义TabBar
ios·swift·wwdc
1024小神1 天前
xcode 配置了AppIcon 但是不显示icon图标
ios·swiftui·swift
奶糖 肥晨1 天前
架构深度解析|基于亚马逊云科技与Swift Alliance Cloud构建高可用金融报文交换架构
科技·架构·swift
Swift社区2 天前
用 Task Local Values 构建 Swift 里的依赖容器:一种更轻量的依赖注入思路
开发语言·ios·swift
TouchWorld2 天前
iOS逆向-哔哩哔哩增加3倍速播放(4)- 竖屏视频·全屏播放场景
ios·swift