Skip to content

Instantly share code, notes, and snippets.

@airspeedswift
Created June 6, 2019 18:37
Show Gist options
  • Save airspeedswift/71ccddc27354be908dd92a52a34a776f to your computer and use it in GitHub Desktop.
Save airspeedswift/71ccddc27354be908dd92a52a34a776f to your computer and use it in GitHub Desktop.
COWed
/// Conform references types for use in the COW wrapper to this protocol
protocol Cowable: class {
/// Make a new unique instance of `copied`
static func makeUnique(_ copied: Self) -> Self
}
/// A wrapper that turns a Cowable reference type into a value-semantic
/// type with access to all of its properties
@dynamicMemberLookup
struct COW<Cowed: Cowable> {
private var _cowed: Cowed
init(_ cowing: Cowed) {
_cowed = cowing
}
public subscript<T>(dynamicMember keyPath: KeyPath<Cowed,T>) -> T {
get { _cowed[keyPath: keyPath] }
}
public subscript<T>(dynamicMember keyPath: WritableKeyPath<Cowed,T>) -> T {
get { _cowed[keyPath: keyPath] }
set {
if !isKnownUniquelyReferenced(&_cowed) { _cowed = .makeUnique(_cowed) }
_cowed[keyPath: keyPath] = newValue
}
}
}
extension COW: CustomStringConvertible where Cowed: CustomStringConvertible {
var description: String { _cowed.description }
}
extension Texture: CustomStringConvertible {
var description: String { "Texture(isSparkly: \(isSparkly)" }
}
final class Texture {
init(isSparkly: Bool) { self.isSparkly = isSparkly }
var isSparkly: Bool
}
extension Texture: Cowable {
static func makeUnique(_ copied: Texture) -> Texture {
Texture(isSparkly: copied.isSparkly)
}
}
struct Material {
public var roughness: Float
public var texture: COW<Texture> = COW(Texture(isSparkly: false))
}
var m1 = Material(roughness: 0.5)
var m2 = m1
m1.texture.isSparkly = true
print(m1)
print(m2)
@ktraunmueller
Copy link

Shouldn't

private var _cowed: Cowed

be

private let _cowed: Cowed

since Cowed is constrained to class types (by Cowable restrictions) anyway?

@lilyball
Copy link

In the talk they used ReferenceWritableKeyPath, but this gist just uses WritableKeyPath. I admit I'm not sure what the practical difference is (the keypath documentation is rather lacking).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment