I'm trying to make a wrapper (property delegate) that would encapsulate how weak references should be compared and hashed.
Consider this example:
class MyClass { }
struct WeakRef: Hashable {
weak var ref: MyClass?
static func == (_ lhs: WeakRef, _ rhs: WeakRef) -> Bool {
return lhs.ref === rhs.ref
}
func hash(into hasher: inout Hasher) {
hasher.combine(ref.map(ObjectIdentifier.init))
}
var hashString: String {
return String(format:"%016X", self.hashValue)
}
}
var strongRefs: [MyClass] = [MyClass(), MyClass()]
let ref1 = WeakRef(ref: strongRefs[0])
let ref2 = WeakRef(ref: strongRefs[0])
let ref3 = WeakRef(ref: strongRefs[1])
print(ref1 == ref2, ref2 == ref3)
print(ref1.hashString, ref2.hashString, ref3.hashString)
strongRefs = []
print(ref1 == ref2, ref2 == ref3)
print(ref1.hashString, ref2.hashString, ref3.hashString)
It prints:
true false
00000000F8F86466 00000000F8F86466 00000000FF656E44
true true
00000000FF863A4C 00000000FF863A4C 00000000FF863A4C
My ref1
and ref2
are declared as immutable, so their hashes should not change and their equality relation should not change either. So that is not a correct implementation.
I could store ObjectIdentifier
in initializer, but still this might break if one object is deallocated and another one is created at exactly the same address.
Ideally, I would like to store something which is distinct for every object, but maintains identity even after object is deallocated, until last weak reference is deallocated.
Based on mikeash.com: Friday Q&A 2017-09-22: Swift 4 Weak References, a reference to the side table looks exactly like what I need - it is unique per object, and keeps living until there are no weak references left. So even if new object is created at the same address, their side tables still would be distinct.
I was able to come up with this:
class MyClass { }
private struct RefHolder {
weak var ref: MyClass?
}
struct WeakRef: Hashable {
var ref: MyClass? {
get { impl.ref }
set {
impl.ref = newValue
updateSideTablePtr()
}
}
private var sideTablePtr: UnsafeRawPointer?
private var impl: RefHolder
init(ref: MyClass?) {
self.impl = RefHolder(ref: ref)
self.updateSideTablePtr()
}
private mutating func updateSideTablePtr() {
self.sideTablePtr = withUnsafeBytes(of: &self.impl) { (ptr: UnsafeRawBufferPointer) in
ptr.bindMemory(to: UnsafeRawPointer?.self)[0]
}
}
static func == (_ lhs: WeakRef, _ rhs: WeakRef) -> Bool {
return lhs.sideTablePtr == rhs.sideTablePtr
}
func hash(into hasher: inout Hasher) {
hasher.combine(sideTablePtr)
}
}
But that's obviously relying on private implementation details. Is there a way to implement this using proper public API?
P.S. Weak references to ObjC objects don't have a side table, so the code above effectively behaves as a solution with saved ObjectIdentifier
.