【问题标题】:Associated type conforming to Protocol and dependency injection issues符合协议和依赖注入问题的关联类型
【发布时间】:2018-04-20 09:19:45
【问题描述】:

我正在研究依赖注入,目前正在更新我的项目以使用它。但是,我遇到了关联类型和协议一致性的问题。

我创建了一个快速演示项目,并创建了一些协议和扩展,以便符合我的协议 ViewModelBased 的视图控制器必须实现关联类型。理想情况下,我希望此关联类型符合 viewModel。这是我目前所拥有的

protocol ViewModel {
    associatedtype Services
    init (withServices services: Services)
}

protocol ViewModelBased: class {
    associatedtype ViewModelType
    var viewModel: ViewModelType { get set }
}

extension ViewModelBased where Self: UIViewController{
    static func instantiateController(with viewModel : ViewModelType) -> Self {

        // I have created UIStoryboard extension to allow for easy opening of view controllers
        // in storyboard
        let viewController : Self = UIStoryboard.mainStoryboard.instantiateViewController()
        viewController.viewModel = viewModel
        return viewController
    }
}

所以我的应用程序中的所有视图模型都符合视图模型,这迫使它们实现服务类型。例如,我的 LoginModel 如下所示

 struct LoginModel : ViewModel{

    // service type
    typealias Services = LoginService

    // init service
    var services : LoginService
    init(withServices services: LoginService) {
        self.services = services
    }

    /// calls login service - attempts login api
    func attemptLogin() {
        services.login()
    }
}

所以这里是一个实现这个的 viewController 的例子

class SecondController: UIViewController, ViewModelBased  {

    var viewModel: LoginModel!

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    @IBAction func loginTest() {
        viewModel.services.onLoginSuccess = { isVerified in

            print(isVerified)
        }

        viewModel.services.onLoginFailure = { errorCode in

            print(errorCode)
        }

        viewModel.attemptLogin()
    }
}

所以把它们放在一起,这允许应用程序初始化一个 viewController 并像这样传递一个 viewModel

   let loginModel = LoginModel(withServices: LoginService())
        let controller = SecondController.instantiateController(with: loginModel)
        self.navigationController?.pushViewController(controller, animated: true)

这一切都很好,但我遇到的问题是,关联的类型目前可以是任何类型。理想情况下,我希望这个 associatedType 符合 ViewModel 协议。但是当我尝试这个时

protocol ViewModelBased: class {
    associatedtype ViewModelType : ViewModel
    var viewModel: ViewModelType { get set }
}

我的 SecondController 现在抛出一个错误,现在强制我初始化 LoginModel

var viewModel : LoginModel = LoginModel(withServices: LoginService())

但这不再使用依赖注入,因为 viewController 现在负责创建 viewModel 实例并知道 viewModel 类的行为。

有什么办法可以解决这个问题吗?如果有人能给我一些关于我做错了什么的信息,我将不胜感激。

【问题讨论】:

    标签: swift swift-protocols


    【解决方案1】:

    您可以扩展Optional 以有条件地符合ViewModel

    extension Optional: ViewModel where Wrapped: ViewModel {
    
        typealias Services = Wrapped.Services
    
        init(withServices services: Services) {
            self = Wrapped(withServices: services)
        }
    
    }
    

    现在您的SecondController 将其模型声明为var viewModel: LoginModel?,默认情况下它初始化为nil,同时仍确保其ViewModelTypeViewModel,而实际上不需要创建它的实例。

    【讨论】:

    • 这是一个有趣的解决方案,但这意味着 viewModel 现在是可选的,因此每次调用时都必须解开 viewModel。我希望我能避免这样的事情。
    • 您使用的是什么版本的 Swift?我正在使用最新的开发快照来试用您的代码,它不会编译 viewModel 被隐式解包,因为 IUO 不能用于满足非可选协议要求(这在 4.1 之前的版本中是可能的,我已经在 4.0.3 中尝试过它并且它有效)。所以这可能是你无论如何都需要做出的改变。
    • 我使用的是最新的 Swift 4.1,我发布的代码对我来说编译得很好。有趣的是它不是为你编译的。
    猜你喜欢
    • 2011-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    相关资源
    最近更新 更多