用委托模式分离问题

在上一篇文章中,我们讨论了Closures的语法以及如何在其接收函数中使用它们。 今天,我们通过确保视图尽可能与控制器分离,来研究一种可用于在应用程序中实施分离和可扩展性的技术。

什么是代表?

委托模式被认为是解决关注点分离的一种可能解决方案,即任何对象都应处理自己的独特问题集。 例如,矩形结构不应处理计算圆的面积,依此类推。 这是一个非常明显的例子,但是委托的思想是一个对象应该使用专门的帮助对象来完成一项任务。

在MVC中,视图应负责向用户显示内容。 由于用户将通过单击,点击或按下视图来与视图进行交互,因此它们还必须接收输入事件。 但是,控制器的责任是实际解释输入并决定采取什么措施来响应输入,这是我们需要真正考虑设计的地方。

那么,如何在不使其中一个过于依赖另一个实现的情况下将视图的输入传递给控制器​​? 我们可以认为Controller在这方面是代表吗?

让我们从代码开始

让我们看一下我们将用于实验的类。 这是我们的BaseController,当我们要扩展应用程序时,它指定了一些不错的功能:

 协议控制器{ 
关联类型ViewType
var typedView:ViewType {get}
}
  class BaseViewController :UIViewController,控制器{ 
覆盖func loadView(){
self.view = ViewType()
}

var typedView:ViewType {
如果让view = self.view为? ViewType {
返回视图
}其他{
让view = ViewType()
self.view =视图
返回视图
}
}
}

这是我们的BaseView,它为我们的View执行相同类型的操作:

  BaseView类:UIView { 
便利init(){
self.init(frame:.zero)
}

覆盖init(frame:CGRect){
super.init(frame:框架)
setupView()
}

需要初始化吗?(编码器aDecoder:NSCoder){
super.init(编码器:aDecoder)
setupView()
}

func setupView(){
//使用此方法进行所有视图之间共享的任何设置,
//例如绘图背景等。
}
}

我们将要使用的实际Controller和View将像这样开始:

 类MyController:BaseViewController  { 

}
 类MyView:BaseView { 
私人让按钮:UIButton = {
let button = UIButton(frame:.zero)
button.setTitle(“这是一个按钮!”,用于:.normal)
button.setTitleColor(.black,for:.normal)
button.setTitleColor(.white,for:.highlighted)
button.backgroundColor = .lightGray
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor
.constraint(equalToConstant:60).isActive = true
button.widthAnchor
.constraint(equalToConstant:200).isActive = true
返回按钮
}()

覆盖func setupView(){
super.setupView()
self.backgroundColor = .white
self.addSubview(self.button)
self.button.centerXAnchor
.constraint(equalTo:self.centerXAnchor).isActive = true
self.button.centerYnchor
.constraint(equalTo:self.centerYAnchor).isActive = true
}
}

UI设计的奇迹,您不同意吗?

我们有一个MyController类,它将实例化一个MyView对象。 MyView仅包含并显示一个按钮,我们将使用该按钮来接收用户输入。

获取用户输入

我们最初的问题是我们希望接收并响应用户的输入,但是我们不希望View侵犯Controller的职责,反之亦然。 我们还希望避免紧密耦合我们的两个类,以防稍后我们替换其中一个。 如果我们向MyView添加以下功能怎么办?

  public func setButtonAction(target:Any ?, action:Selector){ 
self.button.addTarget(target,action:action,for:.touchUpInside)
}

这样,我们可以在MyController中调用以下代码,以对按钮的点击设置适当的响应:

  self.typedView.setButtonAction(target:self,action:#selector(respondToButtonTap(_ :))) 

与将所有View组件保留在我们的控制器中相比,这实际上不是一个糟糕的解决方案。 但是,如果我们连接多个实例以接收来自按钮的信号,我们可能会遇到一些奇怪的错误。 如果释放了这些实例中的任何一个,那么选择器将开始寻找下一个可用对象以接收相同的信号,因此用户输入可能最终对另一个对象进行了超出最初预期的更改。 要了解我的意思,请将MyController类更改为以下类,并查看按下按钮时发生的情况:

 类MyController:BaseViewController  { 
私人var计数器:Int = 0

覆盖func viewDidLoad(){
self.typedView.setButtonAction(target:self,action:#selector(respondToButtonTap(_ :)))
让c = MyController()
self.typedView.setButtonAction(target:c,action:#selector(respondToButtonTap(_ :)))
令d = MyController()
self.typedView.setButtonAction(目标:d,动作:#selector(respondToButtonTap(_ :)))
令e = MyController()
self.typedView.setButtonAction(target:e,action:#selector(respondToButtonTap(_ :)))
让f = MyController()
self.typedView.setButtonAction(target:f,action:#selector(respondToButtonTap(_ :)))
}

@objc func responseToButtonTap(_发件人:UIButton){
计数器+ = 1
打印(计数器)
}
}
s
//点按1可打印“ 1 2 3 4 5”
//点按2可打印“ 6 7 8 9 10”,依此类推

这是一个人为的示例,但它说明了如果在同一按钮中将多个控制器列为目标,将会发生什么情况。 如果这样的错误出现在您的代码中,这将是一个噩梦,尤其是因为在方法返回后直接释放在viewDidLoad()中初始化的Controller时,尤其如此。

委托模式如何?

如果我们将MyView更改为如下所示,该怎么办:

 协议MyViewActionDelegate:AnyObject { 
func buttonWasTapped(_ sender:UIButton)->无效
}
 类MyView:BaseView { 
公共弱变量var buttonActionDelegate:MyViewActionDelegate?

私人让按钮:UIButton = {
let button = UIButton(frame:.zero)
button.setTitle(“这是一个按钮!”,用于:.normal)
button.setTitleColor(.black,for:.normal)
button.setTitleColor(.white,for:.highlighted)
button.backgroundColor = .lightGray
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor
.constraint(equalToConstant:60).isActive = true
button.widthAnchor
.constraint(equalToConstant:200).isActive = true
返回按钮
}()

覆盖func setupView(){
super.setupView()
self.backgroundColor = .white
self.addSubview(self.button)
self.button.centerXAnchor
.constraint(equalTo:self.centerXAnchor).isActive = true
self.button.centerYnchor
.constraint(equalTo:self.centerYAnchor).isActive = true

self.button.addTarget(self,action:#selector(self.delegateButtonTap(_ :)),for:.touchUpInside)
}

@objc私人功能委托委托按钮Tap(_发件人:UIButton){
self.buttonActionDelegate?.buttonWasTapped(发送者)
}
}

和MyController看起来像这样:

 类MyController:BaseViewController ,MyViewActionDelegate { 
覆盖func viewDidLoad(){
self.typedView.buttonActionDelegate =自我
}

func buttonWasTapped(_ sender:UIButton){
//做你的事
}
}

这里发生的是MyView注册为按钮事件的接收者,从而消除了注册多个目标的风险。 然后,MyView通过定义良好的协议将按钮事件委托给单个操作委托。 只要我们遵循Controller的要求,只要它符合MyViewActionDelegate ,它就完全可以并且只发送一个信号。 buttonActionDelegate可选的链接,可以确保仅在buttonActionDelegate拥有对另一个对象的引用时才发送信号。 如果变量为nil ,则该信号将被删除,而在delegateButtonTap(_:)方法中将被忽略。

该解决方案的主要好处是,它使我们的对象保持分离,并且我们的视图不知道控制器的存在。 它仅依赖于协议定义的通道的使用,该通道可能会或可能不会有人监听另一端。 View无关紧要,它将信号转发给可能在那里的任何人,然后继续前进。

本周就这样! 希望这一讨论引发了一些关于如何在自己的项目中使用委托的想法。

如果您有任何疑问,请随时发表评论,然后继续获取有关未来文章的通知。