Home > Blockchain >  Swift generics: how to match associatedtype with generic parameter
Swift generics: how to match associatedtype with generic parameter

Time:02-15

I have an object that lives in my app (say a User object) that I want to be published (because some UI depends on it). I don't want to pass it as an EnvironmentObject because of the Liskov substitution principle so I decided to make a wrapper. Something like this:


protocol HasUser: ObservableObject {
    var user: String { get }
}

class HasUserWrapper: HasUser {
    var user: String { userGetter() }
    
    private let userGetter: () -> String
    
    let objectWillChange: AnyPublisher<Void, Never>
    
    init<UO: HasUser>(wrapping userObject: UO) {
        self.objectWillChange = userObject
            .objectWillChange
            .map { _ in () }
            .eraseToAnyPublisher()
        self.userGetter = { userObject.user }
    }
}

extension HasUser {
    func eraseToHasUserWrapper() -> HasUserWrapper {
        HasUserWrapper(wrapping: self)
    }
}

class ConcreteHasUser: HasUser {
    @Published var user: String = "john"
}

struct UserView {
    @ObservedObject var hasUser: HasUserWrapper = ConcreteHasUser().eraseToHasUserWrapper()
    var body: some View {
        Text(hasUser.user)
    }
}

Now, I would like to make this wrapper generic. So I started something like this:

protocol HasThing: ObservableObject {
    associatedtype Thing
    var thing: Thing { get }
}

class HasThingWrapper<Thing>: HasThing {
    var thing: Thing { thingGetter() }
    private let thingGetter: () -> Foo

    let objectWillChange: AnyPublisher<Void, Never>

    init<HasThingType: HasThing>(wrapping hasThing: HasThingType)  {
        self.objectWillChange = hasThing
            .objectWillChange
            .map { _ in () }
            .eraseToAnyPublisher()
        self.thingGetter = { hasThing.  }
    }
}

extension HasThing {
    func eraseToThingWrapper() -> HasThingWrapper<Thing> {
        HasThingWrapper(wrapping: self)
    }
}

protocol HasString: HasThing {
    var thing: String { get }
}

class StringHolder: HasString {
    @Published var thing: String = "hello"
}

How do I tell the compiler that the generic type Thing inside the wrapper should match the associatedtype Thing in the HasThing protocol?

CodePudding user response:

If I understand correctly, you just need to add a generic constraint in the init:

init<HasThingType: HasThing>(wrapping hasThing: HasThingType)
    where HasThingType.Thing == Thing 
{
    ...
    // then you can do:
    self.thingGetter = { hasThing.thing }
}
  • Related