Last active
November 12, 2022 20:05
-
-
Save IanKeen/f2f3f6ca6850a8b86874560e3b315e13 to your computer and use it in GitHub Desktop.
PropertyWrapper: Add `indirect` functionality to structs (w/ COW)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct Value { | |
var foo: Int | |
@Indirect var inner: Value? | |
} | |
let x = Value(foo: 1, inner: .init(foo: 2, inner: .init(foo: 3, inner: nil))) | |
x.inner?.inner?.foo // 3 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@propertyWrapper | |
public struct Indirect<T> { | |
private var box: Box<T> | |
public var wrappedValue: T { | |
get { box.value } | |
set { | |
if !isKnownUniquelyReferenced(&box) { | |
box = Box(box.value) | |
} else { | |
box.value = newValue | |
} | |
} | |
} | |
public init(wrappedValue: T) { | |
self.box = Box(wrappedValue) | |
} | |
} | |
final class Box<T> { | |
var value: T | |
init(_ value: T) { | |
self.value = value | |
} | |
} | |
extension Indirect: Decodable where T: Decodable { | |
public init(from decoder: Decoder) throws { | |
self.init(wrappedValue: try T(from: decoder)) | |
} | |
} | |
extension Indirect: Encodable where T: Encodable { | |
public func encode(to encoder: Encoder) throws { | |
try self.wrappedValue.encode(to: encoder) | |
} | |
} | |
extension Indirect: Equatable where T: Equatable { | |
public static func ==(lhs: Indirect, rhs: Indirect) -> Bool { | |
return lhs.wrappedValue == rhs.wrappedValue | |
} | |
} | |
// This are needed to deal with wrapping optionals | |
extension KeyedDecodingContainer { | |
public func decode<T: Decodable>(_ type: Indirect<T?>.Type, forKey key: KeyedDecodingContainer.Key) throws -> Indirect<T?> { | |
return Indirect<T?>(wrappedValue: try decodeIfPresent(T.self, forKey: key)) | |
} | |
} | |
extension KeyedEncodingContainer { | |
public mutating func encode<T: Encodable>(_ value: Indirect<T?>, forKey key: KeyedEncodingContainer<K>.Key) throws { | |
guard let value = value.wrappedValue else { return } | |
try encode(value, forKey: key) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks to @woolsweater and @timvermeulen for pointing out I'd need COW here :D