OC

swift当中的copy on write

Posted by sunzhongliang on February 22, 2022

前言

我们都知道,在函数中有两种传值方式: 值类型引用类型, 而值类型有一个copy的操作,它的意思是当你传递一个值类型的变量的时候(给一个变量赋值,或者函数中的参数传值),它会拷贝一份新的值让你进行传递。你会得到拥有相同内容的两个变量,分别指向两块内存。
这样的话,在你频繁操作占用内存比较大的变量的时候就会带来严重的性能问题,Swift 也意识到了这个问题,所以推出了 Copy-on-Write机制,用来提升性能,俗称写时复制

Copy-on-Write

当有一个占用内存很大的一个值类型,并且不得不将它赋值给另一个变量或者当做函数的参数传递的时候,拷贝它的值是一个非常消耗内存的操作,因为你不得不拷贝它所有的东西放置在另一块内存中。 为了优化这个问题,Swift对于一些特定的值类型(集合类型:Array、Dictionary、Set)做了一些优化,在对于 Array 进行拷贝的时候,当传递的值进行改变的时候才会发生真正的拷贝。而对于String、Int等值类型,在赋值的时候就会发生拷贝。下面来看代码验证一下:

import Foundation

// 打印对象的地址
func print(address o: UnsafeRawPointer ) {
    print(String(format: "%p", Int(bitPattern: o)))
}
// 初始话一个array1对象
var array1: [Int] = [0, 1, 2, 3]
var array2 = array1

// 两个对象地址是一样的
print(address: array1) //0x100691520
print(address: array2) //0x100691520

// 此刻我们修改array1
array1.append(4)

// array1的地址发生了变化
print(address: array1) //0x10052d340
// 而array2的地址并没有发生变化
print(address: array2) //0x100691520

Copy-on-Write 如何实现的

可以在OptimizationTips.rst里发现如下代码:

final class Ref<T> {
  var val : T
  init(_ v : T) {val = v}
}

struct Box<T> {
    var ref : Ref<T>
    init(_ x : T) { ref = Ref(x) }

    var value: T {
        get { return ref.val }
        set {
          if (!isUniquelyReferencedNonObjC(&ref)) {
            ref = Ref(newValue)
            return
          }
          ref.val = newValue
        }
    }
}

每当数据被改变,它首先检查它对存储缓冲区的引用是否是唯一的,或者说,检查数组本身是不是这块缓冲区的唯一拥有者。如果是,那么 缓冲区可以进行原地变更;也不会有复制被进行。不过,如果缓冲区有一个以上的持有者,那么数组就需要先进行复制,然后对复制的值进行变化,而保持其他的持有者不受影响

自定义的结构体并不支持Copy-on-Write

作为一个结构体,并不能免费获得写时复制的行为,需要自己进行实现。当自己的类型内部含有一个或多个可变引用,同时想要保持值语义时,应该为其实现写时复制。
为了维护值语义,通常都需要进行在每次变更时,都进行昂贵的复制操作,但是写时复制技术避免了在非必要的情况下的复制操作。

下面是一个简单的示例,采用了一个Ref引用来实现自定义结构体的Copy-on-Write

final class Ref<T> {
  var val: T
  init(_ v: T) {val = v}
}

struct Dog {
    var age: Int
    init(age: Int) {
        self.age = age
    }
}

struct LGPerson<T> {
    private var ref: Ref<T>
    var name: String
    init(name: String, _ ref: T) {
        self.name = name
        self.ref = Ref(ref)
    }

    var dog: T {
        get {return ref.val}
        set {
            if !isKnownUniquelyReferenced(&ref) {
                ref = Ref(newValue)
                return
            }
            ref.val = newValue
        }
    }
}

let dog1 = Dog(age: 2)

var Jack = LGPerson(name: "Jack", dog1)
//print(Jack.dog.description,"\n")

print("Jack-dog age: \((Jack.dog).age)\n")

var Andy = Jack
Andy.name = "Andy"
//print(Andy.dog.description,"\n")

Andy.dog.age = 5

//print(Jack.dog.description,"\n")
//print(Andy.dog.description,"\n")

print("Jack-dog age: \((Jack.dog).age)")
print("Andy-dog age: \((Andy.dog).age)")


苹果在Advice: Use copy-on-write semantics for large values中教我们怎么去使用copy-on-write技术。

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.