如果 var 似乎在 Swift 中深度复制数组。如果让?
If var seems to deep copy arrays in Swift. Does if let?
在Swift 3.0中,下面的代码给出了thisArray[0]
的不同地址,提示数组被深拷贝了。事实确实如此,还是我在分析中遗漏了什么? if let 的行为是否相同?它可能与 if let 无关,因为它是不可变的...
var thisArray: [String]? = ["One", "Two"]
withUnsafePointer(to: &thisArray![0]) {
print("thisArray[0] has address \([=11=])")
}
if var thisArray = thisArray {
withUnsafePointer(to: &thisArray[0]) {
print("thisArray[0] has address \([=11=])")
}
}
相关:https://developer.apple.com/swift/blog/?id=10。
In Swift, Array, String, and Dictionary are all value types.
因此,如果您通过 var
或 let
分配现有 值类型 ,则会发生复制。如果您通过 var
或 let
分配现有 引用类型 (例如 class),那么您将分配一个引用。
大部分是正确的,但掩盖了一些重要的细节...
语义,将值类型分配给不同的绑定(无论是 var
变量还是 let
常量)always 创建一个副本。也就是说,您的程序代码始终可以安全地假设对值类型的一个绑定的修改永远不会影响其他绑定。
或者换一种说法:如果您从头开始构建自己版本的 Swift 编译器/运行时/标准库,您可以让每个 var a = b
为 a
并复制 b
的所有内存内容,而不管 a
和 b
是哪种值类型。在所有其他条件相同的情况下,您的实施将与所有 Swift 程序兼容。
值类型重新分配始终是副本的缺点是对于大型类型(如集合或复合类型),所有复制都会浪费时间和内存。所以...
在实践中,值类型的实现方式可以保持值类型的语义始终是副本保证,同时提供写时复制等性能优化。 Swift 标准库集合类型(数组、字典、集合等)执行此操作,自定义值类型(包括您的)也可以实现写时复制。 (有关如何操作的详细信息,this WWDC 2015 talk 提供了很好的概述。)
要使写时复制工作,实现值类型需要在内部使用引用类型(如 WWDC 谈话中所述)。它必须以这样一种方式来实现,即对值类型的语言保证——赋值总是语义副本——在所有情况下都继续有效。
写时复制数组实现无法保证的一种方式是允许对其底层存储缓冲区进行无保护访问——如果您可以获得指向该存储的原始指针,则可以改变内容以导致其他绑定(即语义副本)发生变异的方式,违反了语言保证。
为了保留写时复制保证,标准库的集合类型确保复制某些可以执行未受保护的变异的操作创建副本。 (尽管如此,有时创建的副本涉及足够的引用操作,以至于副本的内存和时间成本保持较低,直到实际发生突变。)
您可以在 Swift 编译器和标准库源代码中看到它是如何工作的——从 search for isUniquelyReferenced
开始,然后跟随 [= 中的各种用例的调用者和被调用者19=]等
为了说明这里发生了什么,让我们尝试对您的测试进行变体:
var thisArray: [String] = ["One", "Two"]
withUnsafePointer(to: &thisArray[0]) {
print("thisArray[0] has address \([=10=])")
}
var thatArray = thisArray // comment/uncomment here
withUnsafePointer(to: &thisArray[0]) {
print("thisArray[0] has address \([=10=])")
}
当您注释掉赋值 thatArray = thisArray
时,两个地址是相同的。但是,一旦 thisArray
不再被唯一引用,即使访问 原始数组的 底层缓冲区也需要一个副本(或至少一些内部间接寻址)。
在Swift 3.0中,下面的代码给出了thisArray[0]
的不同地址,提示数组被深拷贝了。事实确实如此,还是我在分析中遗漏了什么? if let 的行为是否相同?它可能与 if let 无关,因为它是不可变的...
var thisArray: [String]? = ["One", "Two"]
withUnsafePointer(to: &thisArray![0]) {
print("thisArray[0] has address \([=11=])")
}
if var thisArray = thisArray {
withUnsafePointer(to: &thisArray[0]) {
print("thisArray[0] has address \([=11=])")
}
}
相关:https://developer.apple.com/swift/blog/?id=10。
In Swift, Array, String, and Dictionary are all value types.
因此,如果您通过 var
或 let
分配现有 值类型 ,则会发生复制。如果您通过 var
或 let
分配现有 引用类型 (例如 class),那么您将分配一个引用。
语义,将值类型分配给不同的绑定(无论是 var
变量还是 let
常量)always 创建一个副本。也就是说,您的程序代码始终可以安全地假设对值类型的一个绑定的修改永远不会影响其他绑定。
或者换一种说法:如果您从头开始构建自己版本的 Swift 编译器/运行时/标准库,您可以让每个 var a = b
为 a
并复制 b
的所有内存内容,而不管 a
和 b
是哪种值类型。在所有其他条件相同的情况下,您的实施将与所有 Swift 程序兼容。
值类型重新分配始终是副本的缺点是对于大型类型(如集合或复合类型),所有复制都会浪费时间和内存。所以...
在实践中,值类型的实现方式可以保持值类型的语义始终是副本保证,同时提供写时复制等性能优化。 Swift 标准库集合类型(数组、字典、集合等)执行此操作,自定义值类型(包括您的)也可以实现写时复制。 (有关如何操作的详细信息,this WWDC 2015 talk 提供了很好的概述。)
要使写时复制工作,实现值类型需要在内部使用引用类型(如 WWDC 谈话中所述)。它必须以这样一种方式来实现,即对值类型的语言保证——赋值总是语义副本——在所有情况下都继续有效。
写时复制数组实现无法保证的一种方式是允许对其底层存储缓冲区进行无保护访问——如果您可以获得指向该存储的原始指针,则可以改变内容以导致其他绑定(即语义副本)发生变异的方式,违反了语言保证。
为了保留写时复制保证,标准库的集合类型确保复制某些可以执行未受保护的变异的操作创建副本。 (尽管如此,有时创建的副本涉及足够的引用操作,以至于副本的内存和时间成本保持较低,直到实际发生突变。)
您可以在 Swift 编译器和标准库源代码中看到它是如何工作的——从 search for isUniquelyReferenced
开始,然后跟随 [= 中的各种用例的调用者和被调用者19=]等
为了说明这里发生了什么,让我们尝试对您的测试进行变体:
var thisArray: [String] = ["One", "Two"]
withUnsafePointer(to: &thisArray[0]) {
print("thisArray[0] has address \([=10=])")
}
var thatArray = thisArray // comment/uncomment here
withUnsafePointer(to: &thisArray[0]) {
print("thisArray[0] has address \([=10=])")
}
当您注释掉赋值 thatArray = thisArray
时,两个地址是相同的。但是,一旦 thisArray
不再被唯一引用,即使访问 原始数组的 底层缓冲区也需要一个副本(或至少一些内部间接寻址)。