【问题标题】:Implementing MVP using protocols使用协议实现 MVP
【发布时间】:2022-03-03 19:34:23
【问题描述】:

我正在尝试使用协议来实现 MVP, 我有 View 控制器,它包含对演示者协议的引用。 演示者协议包含对视图的引用 并具有代表 ResultType 的 associatedtype。 每个演示者的 ResultType 都不同。 例如:

class PresenterA: PresenterProtocol {

    weak var view: ViewController!
    
    typealias ResultType = String
    var onDidPressCallback: ((ResultType) -> Void)?

}

也可以

class PresenterB: PresenterProtocol {

    weak var view: ViewController!
    
    typealias ResultType = Apple
    var onDidPressCallback: ((ResultType) -> Void)?

}

当我持有对演示者的引用时,问题就开始了 来自 ViewController。

class ViewController: UIViewController {
    var presenter: PresenterProtocol!
}

当然这是不可能的,我得到了这个错误:

Protocol 'PresenterProtocol' 只能用作通用约束 因为它有 Self 或关联的类型要求

所以我尝试了:

class ViewController<T: PresenterProtocol>: UIViewController {
    var presenter: T!
}

但是现在 PresenterProtocol 有这个问题:

对泛型“ViewController”的引用需要 <...>

中的参数

我做错了什么?我该如何解决?

另外,假设我不支持新版本,所以我不能使用不透明类型(某些关键字)。

【问题讨论】:

  • 从你的描述来看,PresenterProtocol 只是对视图的引用,那你为什么要把它放在视图本身呢?视图可能应该有对演示者输出的引用,更像是委托模式。

标签: swift protocols


【解决方案1】:

在回答您的问题时,当您将视图控制器设为通用时,错误提示“通用类型 'ViewController' 需要 <...> 中的参数”。所以你可以做到这一点:

class PresenterA: PresenterProtocol {
    weak var view: ViewController<PresenterA>!

    ...
}

您可能最终想要创建一个必需的初始化程序,以便您可以实例化这些演示者,例如:

protocol PresenterProtocol {
    associatedtype ResultType

    var onDidPressCallback: ((ResultType) -> Void)? { get set }

    init()
}

class PresenterA: PresenterProtocol {
    weak var view: ViewController<PresenterA>!

    var onDidPressCallback: ((String) -> Void)?

    required init() { ... }
}

class PresenterB: PresenterProtocol {
    weak var view: ViewController<PresenterB>!

    var onDidPressCallback: ((Apple) -> Void)?

    required init() { ... }
}

class ViewController<Presenter: PresenterProtocol>: UIViewController {
    var presenter = Presenter()

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter.onDidPressCallback = { value in
            // show the value in the UI
        }

        ...
    }
}

希望这能回答问题,但这里有一些问题:

  1. Presenters 应该独立于 UIKit。您不想在演示者中引用您的视图控制器(或有 任何 UIKit 依赖项)。只有“视图”(视图、视图控制器等)应该具有 UIKit 依赖项。这种职责分离是 MVP 的核心概念。

    有许多选项可以让演示者通知事件视图(例如委托协议、异步等待等)。但是你有一个闭包变量,这是一个非常合适的方法。演示者不需要对视图控制器的引用,而应该简单地调用适当的闭包。显然,视图控制器只会设置这些闭包变量,以便它可以响应演示者事件。

    但是,正如我所说,有多种不同的方法可以告知演示者触发的事件的“视图”。但是演示者不应该接触到视图控制器本身。不需要或不需要对视图控制器的引用。

  2. 通过使视图控制器成为通用的,它不能再通过标准的 UIKit 机制(故事板、NIB 等)实例化。除非您真的想失去 IB 网点、操作等的好处,并且以编程方式做所有事情,否则您可能不想使其通用。您为了仿制药而引入了大量的开发/维护成本。

    此外,Apple 的视图控制器和String 的视图控制器可能具有独特的控件。一开始,将其设为通用可能会在直觉上吸引人,但在实践中,它会随着时间的推移而分崩离析。这条路走了几次,我最终后悔了。随着 UI 的发展,您最终会随着时间的推移为各个类型细化/专门化 UI。几次尝试这种模式的排列,我发现自己后来把它撕掉了。感觉很直观,但后来经常成为障碍。

  3. 幸运的是,当您从演示者中删除对视图控制器的引用并且不使视图控制器通用时,这些噪音大部分都消失了:

    protocol PresenterProtocol {
        associatedtype ResultType
    
        var onDidPressCallback: ((ResultType) -> Void)? { get set }
    }
    
    class PresenterA: PresenterProtocol {
        var onDidPressCallback: ((String) -> Void)?
    
        // ...
    }
    
    class PresenterB: PresenterProtocol {
        var onDidPressCallback: ((Apple) -> Void)?
    
        // ...
    }
    
    class ViewControllerA: UIViewController {
        var presenter = PresenterA()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            presenter.onDidPressCallback = { value in
                // show the data in the UI
            }
    
            // ...
        }
    }
    

    此时,协议更像是一种合同,以确保所有演示者都遵循共享功能的某些约定。但是我们不再束缚自己,将我们的演示者限制在最不常见的功能上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多