Copy-on-write types and SwiftUI

I am trying to use a custom Copy-on-write type with SwiftUI. I have found that it takes the expensive path every time in the below code. Any thoughts about how I can avoid the copy and deinit for every drag event? Thanks for any insights or hints you might have.

Code Block swift
struct CowType {
  init() {
    self.detail = Detail(values: [])
  }
  private var detail: Detail
  var values: [Int] { detail.values }
  mutating func appendValue(_ value: Int) {
    if !isKnownUniquelyReferenced(&detail) {
      print("performing expensive copy")
      detail = Detail(values: values)
    }
    detail.values.append(value)
  }
  private final class Detail {
    init(values: [Int]) {
      self.values = values
    }
    deinit {
       print("calling deinit")
    }
    var values: [Int]
  }
}
struct ContentView: View {
  @State var state = CowType()
  var body: some View {
    Text("Hello, world! \(state.values.count)")
      .padding()
      .gesture(DragGesture().onChanged { _ in
        state.appendValue(Int.random(in: 1...100))
      })
  }
}

Answered by rayfix in 668551022
As @OOPer indicated, using an object is a way around the problem:

I needed to use this to avoid the expensive copies.

Code Block swift
final class Container: ObservableObject {
private var cow = CowType()
var values: [Int] { cow.values }
func append(_ value: Int) {
objectWillChange.send()
cow.append(value)
}
}


Interestingly, @Published runs into the same problem as @State, making a copy at every iteration. e.g.,

Code Block swift
final class Container: ObservableObject {
  @Published var cow = CowType()
}


This makes cow make a copy every time it is mutated.
An interesting experiment.
It seems as if SwiftUI creates a copy of @State variables when executing actions, so isKnownUniquelyReferenced(_:) would never return true.

Until Apple provides us a way to implement CoW efficiently or updates the implementation of SwiftUI,
you may need to use @StateObject instead of @State where efficiency would be a good concern.
Code Block
class ClassType: ObservableObject {
init() {
self.detail = Detail(values: [])
}
private var detail: Detail
var values: [Int] { detail.values }
func appendValue(_ value: Int) {
objectWillChange.send()
detail.values.append(value)
}
private final class Detail {
init(values: [Int]) {
self.values = values
}
deinit {
print("calling deinit")
}
var values: [Int]
}
}
struct ContentView: View {
//@State var state = CowType()
@StateObject var state = ClassType()
var body: some View {
Text("Hello, world! \(state.values.count)")
.padding()
.gesture(DragGesture().onChanged { _ in
state.appendValue(Int.random(in: 1...100))
})
}
}


Accepted Answer
As @OOPer indicated, using an object is a way around the problem:

I needed to use this to avoid the expensive copies.

Code Block swift
final class Container: ObservableObject {
private var cow = CowType()
var values: [Int] { cow.values }
func append(_ value: Int) {
objectWillChange.send()
cow.append(value)
}
}


Interestingly, @Published runs into the same problem as @State, making a copy at every iteration. e.g.,

Code Block swift
final class Container: ObservableObject {
  @Published var cow = CowType()
}


This makes cow make a copy every time it is mutated.

UPDATE:

On the second inspection, making CowType Equatable does not fix the issue.

On the Swift.org forum, Jonathan Hise Kaldma thought that making CowType Equatable might solve the problem. It was a good thought, but didn't work out in this case.
Copy-on-write types and SwiftUI
 
 
Q