Hi,
I was wondering if anybody had any ideas about how to solve this problem or find an elegant workaround.
I am trying to take a single protocol that has associated types and make it composable by splitting out some "not always required" parts of the protocol into other protocols.
The path to this appears to be to define the "companion" protocols with the same associated type names, and it largely works so long as any type constraints on the associated types in both protocols are met – which is pretty amazing.
However the problem comes when the protocol (in this case Action
) is used as a generic constraint in function in another protocol (see Observer
below). As soon as you do this, implementations of Observer
can not use anything from the composed protocols as you cannot do if let x = input as? OtherComposableProtocolWithSameAssociatedType
because that composable protocol has associated type requirements, and hence cannot be used in the function.
Is there any way to achieve this? See sample below.
protocol Action {
associatedtype Input
associatedtype Presenter
static func present(i: Input, p: Presenter)
}
extension Action {
static func present(i: TestAction.Input, p: TestAction.Presenter) {
print(i)
}
}
protocol AnalyticsTrackable {
associatedtype Input where Input: CustomStringConvertible
static var analyticsID: String { get }
static func attributes(for i: Input)
}
extension AnalyticsTrackable {
static func attributes(for i: TestAction.Input) {
}
}
class SomeInput: CustomStringConvertible {
let value: String = "something"
var description: String { return value }
}
final class TestAction: Action, AnalyticsTrackable {
typealias Input = SomeInput
typealias Presenter = Any
static var analyticsID = "test"
}
protocol Observer {
func actionDidComplete<T>(action: T.Type) where T: Action
}
class TestObserver: Observer {
func actionDidComplete<T>(action: T.Type) where T: Action {
print("Completed: \(action)")
// This is the part that breaks, as expected with PATs, but it's not clear if there is any workaround.
// You cannot overload `actionDidComplete` for different type constraints as Swift will not select the correct one
if let analyticsAction = action as? AnalyticsTrackable.Type {
print("Analytics ID: \(analyticsAction.analyticsID)")
}
}
// This does NOT work, as it understandably only resolves to the statically type method generic on Action:
/*
func actionDidComplete<T>(action: T.Type) where T: Action {
func actionDidComplete<T>(action: T.Type) where T: Action {
print("Completed: \(action)")
}
func actionDidComplete<T>(action: T.Type) where T: Action & AnalyticsTrackable {
print("Completed: \(action)")
print("Analytics ID: \(action.analyticsID)")
}
*/
}
let observer = TestObserver()
func dispatch<T>(action: T.Type, input: T.Input, presenter: T.Presenter) where T: Action {
action.present(i: input, p: presenter)
observer.actionDidComplete(action: action)
}
dispatch(action: TestAction.self, input: SomeInput(), presenter: "UI")