I'm trying to use SE-0142 (Associated Type Constraints) to make an Observer pattern with 2 protocols, IsObserver
(like a client) and HasObservers
(like a server), where there's a shared DataType
that represents the type of the thing being observed.
I need objects conforming to HasObservers
to be capable of being a struct or a class, and I want the IsObserver
to be intentionally limited to be a class (want, but do not need).
I'm not good with generics... after several hours I got this far, with the compiler error in a comment inline below. I'm stuck and not sure where to go next, and I'm not sure this approach is even possible or reasonable. All help much appreciated!
import Foundation
protocol IsObserver: class {
associatedtype DataType
func dataDidUpdate(_ data: [DataType])
}
struct Observation<T: IsObserver> {
weak var observer: T?
}
protocol HasObservers {
associatedtype DataType : IsObserver where DataType.DataType == DataType
static var observations: [ObjectIdentifier : Observation<IsObserver>] { get set } // ERROR: "Value of protocol type 'IsObserver' cannot conform to 'IsObserver'; only struct/enum/class types can conform to protocols"
static func tellObserversDataDidUpdate(_ data: [DataType])
}
extension HasObservers {
static func tellObserversDataDidUpdate(_ data: [DataType]) {
for (id, observation) in observations {
guard let observer = observation.observer else {
observations.removeValue(forKey: id)
continue
}
observer.dataDidUpdate(data)
}
}
static func addObserver<T: IsObserver>(_ observer: T) {
let id = ObjectIdentifier(observer)
let ob = Observation.init(observer: observer)
observations[id] = ob
}
static func removeObserver<T: IsObserver>(_ observer: T) {
let id = ObjectIdentifier(observer)
observations.removeValue(forKey: id)
}
}
UPDATE: Alright, got there in the end. Was harder than I thought and required type erasure. In this gist there are two versions: the first one is the one with associatedtype protocols per the original question. It is limited though - the object that is the observer can only observe one type. So I made another variant that can have multiple types but doesn't use associatetype protocols so the observer has to check the type manually.
https://gist.github.com/xaphod/4f8a6402429759b6b3fd8ea2d8ea53c4
I'll simplify your use case a bit (ignore observations) to hopefully get the concept across.
HasObservers
basically has 2 associated types - the DataType
and the IsObserver
type, and then you'd constrain the IsObserver
type to have the right DataType
protocol IsObserver {
associatedtype DataType
func dataDidUpdate(_ data: [DataType])
}
protocol HasObservers {
associatedtype DataType
associatedtype ObserverType: IsObserver where ObserverType.DataType == DataType
static func addObserver(_ observer: ObserverType)
static func tellObserversDataDidUpdate(_ data: [DataType])
// ..
}