Skip to content

Instantly share code, notes, and snippets.

@xtravar
Last active June 9, 2024 14:43
Show Gist options
  • Save xtravar/7ca65b6d6cdd7ced4858074c065ac6a1 to your computer and use it in GitHub Desktop.
Save xtravar/7ca65b6d6cdd7ced4858074c065ac6a1 to your computer and use it in GitHub Desktop.
Use classes and objects as keys to dictionaries and sets.
/*
Swift AnyObject and AnyClass are not Hashable, so they cannot be used in Dictionaries as keys or Sets as members.
This code concisely shows how to make wrapper structs for these types and then convenience subscripts/methods to wrap/unwrap.
This is for the case where you wish to associate a reference, not object content, with a value or used in a set.
Example use case: a dependency injector that returns an object for a protocol.
*/
// protocol for class and object struct wrappers
public protocol HashableObjectWrapper : Hashable {
associatedtype ObjectType
var value: ObjectType { get }
init(_ value: ObjectType)
static func identityEqual(_ lhs: ObjectType, _ rhs: ObjectType) -> Bool
static func createIdentifier(_ value: ObjectType) -> ObjectIdentifier
}
// default impl - not necessarily efficient unless the compiler is super smart
extension HashableObjectWrapper {
@_transparent
public static func identityEqual(_ lhs: ObjectType, _ rhs: ObjectType) -> Bool {
let id1 = self.createIdentifier(lhs)
let id2 = self.createIdentifier(rhs)
return id1 == id2
}
}
// implement Equatable and Hashable
extension HashableObjectWrapper {
@_transparent
public static func == (lhs: Self, rhs: Self) -> Bool {
return Self.identityEqual(lhs.value, rhs.value)
}
@_transparent
public func hash(into hasher: inout Hasher) {
Self.createIdentifier(self.value).hash(into: &hasher)
}
}
// wraps a class so it can be a key to a dictionary or set
// note: we can't genericize this because Swift doesn't like that (yet?)
public struct ClassWrapper : HashableObjectWrapper {
@_transparent
public static func createIdentifier(_ value: AnyClass) -> ObjectIdentifier {
return ObjectIdentifier(value)
}
public let value: AnyClass
public init(_ value: AnyClass) {
self.value = value
}
}
// wraps an object so it can be a key to a dictionary or set
public struct ObjectWrapper<T: AnyObject> : HashableObjectWrapper {
@_transparent
public static func createIdentifier(_ value: T) -> ObjectIdentifier {
return ObjectIdentifier(value)
}
public let value: T
public init(_ value: T) {
self.value = value
}
}
// allows using objects/classes directly as arguments to Set
extension Set where Element: HashableObjectWrapper {
@discardableResult
public mutating func insert<T>(_ newMember: T) -> (inserted: Bool, memberAfterInsert: T) where T == Element.ObjectType {
let result = self.insert(Element(newMember))
return (inserted: result.inserted, memberAfterInsert: result.memberAfterInsert.value)
}
@discardableResult
public mutating func remove<T>(_ member: T) -> T? where T == Element.ObjectType {
let removed = self.remove(Element(member))
return removed?.value
}
}
// allows using objects directly as keys into a dictionary
extension Dictionary where Key: HashableObjectWrapper {
public subscript<K: AnyObject>(key: K) -> Value? where K == Key.ObjectType {
get {
return self[Key(key)]
}
set {
self[Key(key)] = newValue
}
}
public subscript<K: AnyObject>(key: K, default defaultValue: @autoclosure () -> Value) -> Value where K == Key.ObjectType {
get {
return self[Key(key), default: defaultValue]
}
set {
self[Key(key), default: defaultValue] = newValue
}
}
}
// allows using classes directly as a key into a dictionary
extension Dictionary where Key == ClassWrapper {
public subscript<K: AnyObject>(key: K.Type) -> Value? {
get {
return self[Key(key)]
}
set {
self[Key(key)] = newValue
}
}
public subscript<K: AnyObject>(key: K.Type, default defaultValue: @autoclosure () -> Value) -> Value {
get {
return self[Key(key), default: defaultValue]
}
set {
self[Key(key), default: defaultValue] = newValue
}
}
}
// turn an array of objects into wrapped objects
extension Array where Element: HashableObjectWrapper {
public init<O: Sequence>(_ unwrappedSequence: O) where O.Element == Element.ObjectType {
self = unwrappedSequence.map { Element($0) }
}
}
// turn an array of wrapped objects into objects
extension Array {
public init<O: Sequence>(_ wrappedSequence: O) where O.Element: HashableObjectWrapper, O.Element.ObjectType == Element {
self = wrappedSequence.map { $0.value }
}
}
// convenience typealiases
public typealias ObjectSet<T: AnyObject> = Set<ObjectWrapper<T>>
public typealias ObjectKeyedDictionary<K: AnyObject, V> = Dictionary<ObjectWrapper<K>, V>
public typealias ClassSet = Set<ClassWrapper>
public typealias ClassKeyedDictionary<V> = Dictionary<ClassWrapper, V>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment