Swift has value types and reference types, and value types are copied when assigned or passed to functions. When writing code, are these value types copied in memory each time an assignment is passed?

The answer is no. If you have an array of thousands of elements and you copy it to another variable, Swift has to copy all the elements, even though the two variables have exactly the same array contents, which is bad for its performance.

Structures and Enumerations Are Value Types explicitly mentioned that their implementation has been optimized to avoid unnecessary replication:

Collections defined by the standard library like arrays, dictionaries, and strings use an optimization to reduce the performance cost of copying. Instead of making a copy immediately, these collections share the memory where the elements are stored between the original instance and any copies. If one of the copies of the collection is modified, the elements are copied just before the modification. The behavior you see in your code is always as if a copy took place immediately.

With COW, when two variables point to the same array, they point to the same underlying data. Swift makes a copy only when the second variable is modified; the first variable does not change.

  • Ensure that there is no wasted work by delaying the copying operation until it is actually used.
  • Allows value types to be copied multiple times without incurring extra memory, adding overhead only when they change. So memory is used more efficiently.

Let’s verify the above:

Basic types (Int, String, etc.):

import Foundation

var num1 = 101
var num2 = num1
print(address(of: &num1)) //0x108074090
print(address(of: &num2)) //0x108074098

var str1 = "oldbirds"
var str2 = str1
print(address(of: &str1)) //0x1080740a0
print(address(of: &str2)) //0x1080740b0

// Prints the memory address
func address(of object: UnsafeRawPointer) -> String {
    let addr = Int(bitPattern: object)
    return NSString(format: "%p", addr) as String
}
Copy the code

Collection types

var arr1 = [1.2.3.4.5]
var arr2 = arr1
print(address(of: &arr1)) //0x600000e55510
print(address(of: &arr2)) //0x600000e55510

arr2[2] = 4
print(address(of: &arr1)) //0x600000e55510
print(address(of: &arr2)) //0x600000e55dd0
Copy the code

Custom type

COW is a feature specifically added to Swift arrays and dictionaries, and custom data types are not automatically implemented.

struct Person {
    var name = ""
}
var p1 = Person(name: "oldbirds")
print(address(of: &p1)) // 0x101ab32d0
var p2 = p1
print(address(of: &p2)) // 0x101ab32e0
p2.name = "like"
print(address(of: &p2)) // 0x101ab32e0
Copy the code

As you can see from the above code, even though P1 is assigned to P2, their memory addresses are still different. This shows that custom constructs do not support copy-on-write.

How is copy-on-write implemented

You can find the following code in 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 (!isKnownUniquelyReferenced(&ref)) {
            ref = Ref(newValue)
            return
          }
          ref.val = newValue
        }
    }
}
Copy the code

IsKnownUniquelyReferenced used to check an instance is the only reference.

This example shows how to use a reference type to implement a generic value type T with copy-on-write. When you call a set, determine if there are multiple references. If there are multiple references, copy them.

struct Persion {
    var name = "oldbirds"
}
let oldbirds = Persion(a)var box = Box(oldbirds)
var box2 = box // Box2 shares box.ref with box
print(box.value.name) // oldbirds
print(box2.value.name) // oldbirds

box2.value.name = "like" // box2 creates a new ref
print(box.value.name) // oldbirds
print(box2.value.name) // like
Copy the code

This technique is extensively used in the Swift standard library.

With the above technical theory, we can apply COW technology together:

import UIKit
import PlaygroundSupport

final class Box<A> {
  var value: A
  init(_ value: A) {
    self.value = value
  }
}

/// Gaussian blur
struct GaussianBlur {
    private var boxedFilter: Box<CIFilter> = {
        var filter = CIFilter(name: "CIGaussianBlur", parameters: [:])!
        filter.setDefaults()
        return Box(filter)
    }()

    private var filter: CIFilter {
        get { boxedFilter.value }
        set { boxedFilter = Box(newValue) }
    }

    private var filterForWriting: CIFilter {
        mutating get {
          if !isKnownUniquelyReferenced(&boxedFilter) {
            filter = filter.copy() as! CIFilter
            print("😄 copy filter,\(address(of: &self))")}else {
            print("Share the filter,\(address(of: &self))")}return filter
        }
    }

    var inputImage: CIImage {
        get { return filter.value(forKey: kCIInputImageKey) as! CIImage }
        set { filterForWriting.setValue(newValue, forKey: kCIInputImageKey) }
    }

    var radius: Double {
        get { return filter.value(forKey: kCIInputRadiusKey) as! Double }
        set { filterForWriting.setValue(newValue, forKey: kCIInputRadiusKey) }
    }

    var outputImage: CIImage? {
      return filter.outputImage
    }
}

let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 660))

let imgUrl = Bundle.main.url(forResource: "6924717", withExtension: "jpeg")!
let beginImage = CIImage(contentsOf: imgUrl)!
var gaussianBlur = GaussianBlur()
gaussianBlur.radius = 5 / / Shared
gaussianBlur.inputImage = beginImage / / Shared
let filterImg = UIImageView(frame: CGRect(x: 10, y: 10, width: 300, height: 200))
filterImg.image = UIImage(ciImage: gaussianBlur.outputImage!)
view.addSubview(filterImg)

print("\n")
var gaussianBlur2 = gaussianBlur
gaussianBlur2.radius = 10
gaussianBlur2.inputImage = beginImage
let filterImg2 = UIImageView(frame: CGRect(x: 10, y: 220, width: 300, height: 200))
filterImg2.image = UIImage(ciImage: gaussianBlur2.outputImage!)
view.addSubview(filterImg2)
PlaygroundPage.current.liveView = view

print("\n")
var gaussianBlur3 = gaussianBlur
gaussianBlur3.radius = 2
let filterImg3 = UIImageView(frame: CGRect(x: 10, y: 440, width: 300, height: 200))
filterImg3.image = UIImage(ciImage: gaussianBlur3.outputImage!)
view.addSubview(filterImg3)
PlaygroundPage.current.liveView = view

print("OK")
Copy the code

Output result:

Share the filter,0x107547678Share the filter,0x107547678Share the filter,0x107547678Share the filter,0x107547678😄 Copy filter,0x107547688Share the filter,0x107547688Share the filter,0x107547688Share the filter,0x107547688😄 Copy filter,0x107547698Share the filter,0x107547698
OK
Copy the code

Conclusion:

  • Copy-on-write is a mechanism used to optimize Copy operations for value types that consume large amounts of memory.
  • For primitive value types such as Int, Double, String, etc., they are copied when assigned.
  • For Array, Dictionary, and Set types, copying does not occur when they are assigned, but only after they are modified.
  • COW is not automatically implemented for custom data types, but can be implemented on demand.