在Swift中,structs
是价值类型。如果我有一个包含大数据的结构(假设)并且我将结构传递给许多不同的函数,那么每次都会复制结构吗?如果我同时调用它,那么内存消耗会很高吗?
从理论上讲,如果你传递非常大的struct
s导致它们被复制,可能会有内存问题。几点警告/观察:
String
,Array
,Set
,Dictionary
,Data
等,并且那些具有“写入时复制”(CoW)行为。这意味着如果你复制了struct
,整个对象不一定被复制,而是在内部采用类似引用的行为,以避免不必要的重复,同时仍然保留值类型语义。但是如果你改变了有问题的对象,那么只有这样才能复制。
这是两个世界中最好的,您可以享受价值语义(无意外共享),而不会对这些特定类型进行不必要的重复数据。
考虑:
struct Foo {
private var data = Data(repeating: 0, count: 8_000)
mutating func update(at: Int, with value: UInt8) {
data[at] = value
}
}
此示例中的私有Data
将采用CoW行为,因此当您制作Foo
实例的副本时,大型有效负载将不会被复制,直到您将其变异为止。
最后,您问了一个假设的问题,答案实际上取决于您的大型有效负载所涉及的类型。但对于许多原生的Swift类型,它通常不是问题。struct
由不使用CoW的类型组成(即,不是上述可扩展的Swift类型之一); (c)您希望继续享受价值语义(即不转换为具有意外共享风险的参考类型)。在WWDC 2015视频Building Better Apps with Value Types中,他们向我们展示了如何自己使用CoW模式,避免不必要的副本,同时在对象发生变异时仍然执行真正的值类型行为。
考虑:
struct Foo {
var value0 = 0.0
var value1 = 0.0
var value2 = 0.0
...
}
您可以将它们移动到私有引用类型:
private class FooPayload {
var value0 = 0.0
var value1 = 0.0
var value2 = 0.0
...
}
extension FooPayload: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
let object = FooPayload()
object.value0 = value0
...
return object
}
}
然后,您可以更改您的公开值类型以使用此私有引用类型,然后在任何变异方法中实现CoW语义,例如:
struct Foo {
private var _payload: FooPayload
init() {
_payload = FooPayload()
}
mutating func updateSomeValue(to value: Double) {
copyIfNeeded()
_payload.value0 = value
}
private mutating func copyIfNeeded() {
if !isKnownUniquelyReferenced(&_payload) {
_payload = _payload.copy() as! FooPayload
}
}
}
copyIfNeeded
方法执行CoW语义,使用isKnownUniquelyReferenced
仅复制该有效负载未被唯一引用。
这可能有点多,但它说明了如果您的大型有效载荷尚未使用CoW,如何在您自己的值类型上实现CoW模式。但是,如果(a)您的有效载荷很大,我只建议这样做; (b)您知道相关的有效载荷属性尚未支持CoW,并且(c)您已确定您确实需要该行为。这实际上取决于两个主要因素:结构传递的次数,以及结构“保持活动”的时间长度(a.k.a它们是否也被ARC快速清理?)。
可以使用以下公式计算内存消耗总量:mem_usage = count * struct_size
其中count是在任何给定时刻“活着”的结构总数。如果结构仍然存活或快速清理,您需要自己做出判断。
是。如果它们在相同的范围内,它将会因为structs
在超出范围后被解除分配,所以它们被释放了它们所在的任何基类,但是在范围内具有相同值的太多可以加起来为你制造一个问题所以要小心,this也是一篇很好的文章,详细讨论了这些主题。
你也不能把deinit
放在一个结构中直接看这个,但有一个解决方法。你可以创建一个struct
,它引用了一个在解除分配时打印出来的类,如下所示:
class DeallocPrinter {
deinit {
print("deallocated")
}
}
struct SomeStruct {
let printer = DeallocPrinter()
}
func makeStruct() {
var foo = SomeStruct()
}
makeStruct() // deallocated becasue it escaped the scope