@dynamicMemberLookup
struct Reference<Value> {
var value: UnsafeMutablePointer<Value>!
init(_ v: Value) {
// 🤔 which is better this:
withUnsafePointer(to: v) { p in
value = UnsafeMutablePointer<Value>.allocate(capacity: 1)
value.initialize(from: p, count: 1)
}
// or this?
/*
var v = v
value = UnsafeMutablePointer<Value>.allocate(capacity: 1)
value.initialize(from: &v, count: 1)
*/
}
mutating func deallocate() {
value.deinitialize(count: 1)
value.deallocate()
value = nil
}
/* is this subscript needed??? 🤔
subscript<T>(dynamicMember keyPath: KeyPath<Value, T>) -> T {
value.pointee[keyPath: keyPath]
}
*/
subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> T {
get {
value.pointee[keyPath: keyPath]
}
nonmutating set {
value.pointee[keyPath: keyPath] = newValue
}
}
}
Usage example:
struct ExampleType {
var x: Int
var y: Double
}
func foo(_ v: Reference<ExampleType>) {
v.x = 456
}
var v = Reference(ExampleType(x: 123, y: 3.14))
print(v.x) // 123
foo(v)
print(v.x) // 456
print(v.y) // 3.14
v.deallocate()
print(v.x) // Swift runtime failure
It's quite concise.
Btw, which version of "init" implementation above is better performance wise, or are they equivalent? Is the "KeyPath" subscript version needed? (the sample compiles just fine without it).
My first iteration of this idea was very similar to your "Reference" type! The issue I had with it was that it led to a lot of extra code to deal with types.
struct ObjStorage {
let i: Int
}
let myObject: Ref<ObjStorage> = .init(ObjStorage(x: 5))
func doSomething(with obj: Ref<ObjStorage>) { ... }
I have a system of objects right now in a performance critical section which I know do not need ARC, so this macro was my solution to getting rid of all the verbose wrapper types.
The syntax overhead is small. I'd be more concerned about dynamic member lookup overhead of my version, that's likely to be much bigger than ARC overhead.
I think dynamic member has 0 overhead as it’s resolved at compile time. I personally find dynamic members to be an annoying Swift feature because the autocomplete doesn’t handle it well but it’s just personal preference
I do not think that avoiding ARC is a big advantage here. You use classes if it fits the purpose, a good fit would be if you would like to work with references and you have a natural hierarchy of classes. I think that inheritance is kind of expensive, but in many cases you can attenuate this to some degree by declaring your classes final. From some tests that I made some time ago, the difference to working with structs is then still there, but this is in the range of maybe a 20 % performance hit (sorry I do not remember the exact numbers and I do have any code to share).
So when classes fit your problem space well and final (or in some cases private) classes are a good option, I think classes are fine and the performance is good, I see this as a good tradeoff then. I would not use any further “tricks” here. Else, you should use structs.
I think you're right that this is bad idea. However, I'm working on my Swift interpreter and need to avoid the unnecessary book keeping with native Swift classes to help performance out.
One thing I’ve noticed is that ~Copyable entirely prevents you from getting a pointer if you do happen to need one, which was both surprising and frustrating.
Very interesting findings! Do I understand correctly that simply going from classes to manually allocated structs in the heap it went from 0.57 to 0.22? Maybe there's extra bookkeeping associated with classes other than retain/release?