简体   繁体   中英

Constraint generic function by comparing instance property type with generic parameter

I want to subclass ViewControllers to create a generic Coordinator class. This coordinator class should be able to type safely inject dependencies to child coordinator classes. Child coordinators should only have access to explicitly defined dependencies. I made the following working playground with abstract classes to layout the problem. I am open to other ideas how to solve the described problem.

Prerequisites

import Foundation

protocol HasFirstDependency {
    var first: Any? { get }
}

protocol HasSecondDependency {
    var second: Any? { get }
}

typealias AllDependencies = HasFirstDependency & HasSecondDependency

struct AppDependencies: AllDependencies {
    var first: Any?
    var second: Any?
}



class Coordinator<D> {
    var dependencies: D?
}

extension Coordinator {

    static func initialize() -> Coordinator<D> {
        return Coordinator<D>()
    }

}

class ParentCoordinator: Coordinator<AllDependencies> {
    var children: [Any] = []
}

class FirstChildCoordinator: Coordinator<HasFirstDependency> {}

class SecondChildCoordinator: Coordinator<HasSecondDependency> {}

Question

The following code outlines the question (see comments 1. and 2.). Is there anyway to avoid casting and restrict the childType at compile time in the described way?

extension ParentCoordinator {

    func add<C: Coordinator<D>, D>(childType: C.Type) { // 2. ... by setting a constraint like this: "where self.dependecies is D?"
        let child = C.initialize()
        children.append(child)
        if let dependencies: D? = self.dependencies as? D? { // 1. is there any way to avoid this casting ...
            child.dependencies = dependencies
        } else {
            assertionFailure("parentCoordinator does not hold child dependencies")
        }
    }

}

let parent = ParentCoordinator()
parent.dependencies = AppDependencies(first: "bla", second: "blup")
parent.add(childType: FirstChildCoordinator.self)
let child = parent.children.first as? Coordinator<HasFirstDependency>
print(type(of: parent.dependencies)) // Optional<HasFirstDependency & HasSecondDependency>
print(parent.dependencies?.first) // Optional("bla")
print(parent.dependencies?.second) // Optional("blup")
print(type(of: child?.dependencies)) // Optional<HasFirstDependency>
print(child?.dependencies?.first) // Optional("bla")
//print(child?.dependencies?.second) // does not compile, what I wanted to achieve

What I want

To be more specific: The following fatal error is, what I want to catch at compile time.

protocol ForgottenDependency {
    var forgotten: Any? { get }
}

class ThirdChildCoordinator: Coordinator<ForgottenDependency> {}

parent.add(childType: ThirdChildCoordinator.self) // Fatal error: parentCoordinator does not hold child dependencies: file MyPlayground.playground, line 49

What you are requesting is some kind of logical OR on type constraints:

... where D == HasFirstDependency || HasSecondDependency

Disjunction of type constraints is not possible in swift. To quote the commonly rejected changes :

Disjunctions (logical ORs) in type constraints: These include anonymous union-like types (eg (Int | String) for a type that can be inhabited by either an integer or a string). "[This type of constraint is] something that the type system cannot and should not support."

One possible solution can be to make dependencies conform to a common protocol. This, to be honest, does not exactly solve the problem of detecting "forgotten" dependencies, since all of the dependencies would have to implement this common protocol. This approach was discussed here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM