首页 iOS.& Swift Books 教程设计模式

4
委托模式 由约书亚格林写

委派模式使一个对象能够使用另一个“帮助程序”对象来提供数据或执行任务而不是执行任务本身。此模式有三个部分:

  • 存在 需要委托的对象,也称为 委派对象。 这是对象 加强 委托。委托通常作为弱属性保存,以避免委托对象保留委托的保留周期,该委员会保留委托委托对象。

  • A 委托协议,它定义了委托可以或应该实施的方法。

  • A 代表,它是实现委托协议的辅助对象。

通过依赖委托协议而不是具体对象,实现更灵活: 任何 实现协议的对象可以用作委托!

你什么时候应该用它?

Use this pattern to break up large classes or create generic, reusable components. Delegate relationships are common throughout Apple frameworks, especially UIKit. Both DataSource- and Delegate-named objects actually follow the delegation pattern, as each involves one object asking another to provide data or do something.

为什么在Apple框架中,在苹果框架中不仅仅是一个协议,而不是两个协议?

Apple frameworks commonly use the term DataSource to group delegate methods that 提供 data. For example, UITableViewDataSource is expected to provide UITableViewCells to display.

Apple frameworks typically use protocols named Delegate to group methods that 收到 data or events. For example, UITableViewDelegate is notified whenever a row is selected.

It’s common for the dataSource and 代表 to be set to the 相同的 object, such as the view controller that owns a UITableView. However, they don’t have to be, and it can be very beneficial at times to have them set to different objects.

操场例子

我们来看看一些代码!

import UIKit

public class MenuViewController: UIViewController {
  
  // 1
  @IBOutlet public var tableView: UITableView! {
    didSet {
      tableView.dataSource = self
      tableView.delegate = self
    }
  }
  
  // 2
  private let items = ["Item 1", "Item 2", "Item 3"]
}
// MARK: - UITableViewDataSource
extension MenuViewController: UITableViewDataSource {
  
  public func tableView(_ tableView: UITableView,
                 cellForRowAt indexPath: IndexPath)
    -> UITableViewCell {
      let cell =
        tableView.dequeueReusableCell(withIdentifier: "Cell",
                                      for: indexPath)
      cell.textLabel?.text = items[indexPath.row]
      return cell
  }
  
  public func tableView(_ tableView: UITableView,
                 numberOfRowsInSection section: Int) -> Int {
    return items.count
  }
}

// MARK: - UITableViewDelegate
extension MenuViewController: UITableViewDelegate {
  
  public func tableView(_ tableView: UITableView,
                 didSelectRowAt indexPath: IndexPath) {
    // To do next....
  }
}
public protocol MenuViewControllerDelegate: class {
  func menuViewController(
    _ menuViewController: MenuViewController,
    didSelectItemAtIndex index: Int)
}
public weak var delegate: MenuViewControllerDelegate?
代表?.menuViewController(self,
  didSelectItemAtIndex: indexPath.row)

你应该小心吗?

代表非常有用,但它们可以过度使用。小心创造 太多 对象的代表。

教程项目

The playground example has given you a small taste for what it looks like to implement the delegation pattern. It’s now time to take that theory and make use of it in an app. You’LL从上一章继续rabbleWabble应用程序,然后添加菜单控制器以选择问题组。

import UIKit

public class SelectQuestionGroupViewController: UIViewController {
  
  // MARK: - Outlets
  @IBOutlet internal var tableView: UITableView! {
    didSet {
      tableView.tableFooterView = UIView()
    }
  }
  
  // MARK: - Properties
  public let questionGroups = QuestionGroup.allGroups()
  private var selectedQuestionGroup: QuestionGroup!
}
// MARK: - UITableViewDataSource
extension SelectQuestionGroupViewController: UITableViewDataSource {
  
  public func tableView(_ tableView: UITableView,
                        numberOfRowsInSection section: Int)
                        -> Int {
    return questionGroups.count
  }
  
  public func tableView(_ tableView: UITableView,
                        cellForRowAt indexPath: IndexPath)
                        -> UITableViewCell {
    return UITableViewCell()
  }
}
import UIKit

public class QuestionGroupCell: UITableViewCell {
  @IBOutlet public var titleLabel: UILabel!
  @IBOutlet public var percentageLabel: UILabel!
}
public func tableView(_ tableView: UITableView,
                      cellForRowAt indexPath: IndexPath)
                      -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(
    withIdentifier: "QuestionGroupCell") as! QuestionGroupCell
  let questionGroup = questionGroups[indexPath.row]
  cell.titleLabel.text = questionGroup.title
  return cell
}

设置视图

打开 main.storyboard.,选择 对象库按钮 并进入 UIViewController. 进入 搜索字段 在出现的新窗口中。

显示所选问题组

打开 main.storyboard. again and select the SelectQuestionGroupViewController scene. Press the 编辑 菜单按钮,然后 嵌入▸导航控制器.

// MARK: - UITableViewDelegate
extension SelectQuestionGroupViewController: UITableViewDelegate {
  
  // 1
  public func tableView(_ tableView: UITableView,
                        willSelectRowAt indexPath: IndexPath)
                        -> IndexPath? {
    selectedQuestionGroup = questionGroups[indexPath.row]
    return indexPath
  }
  
  // 2
  public func tableView(_ tableView: UITableView,
                        didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
  }
  
  // 3
  public override func prepare(for segue: UIStoryboardSegue,
                               sender: Any?) {
    guard let viewController = segue.destination
      as? QuestionViewController else { return }
    viewController.questionGroup = selectedQuestionGroup
  }
}

创建自定义委托

该应用程序开始前进,但仍有一些东西缺少:

public var questionGroup = QuestionGroup.basicPhrases()
public var questionGroup: QuestionGroup! {
  didSet {
    navigationItem.title= questionGroup.title
  }
}

private lazy var questionIndexItem: UIBarButtonItem = {
  let item = UIBarButtonItem(title: "",
                             style: .plain,
                             target: nil,
                             action: nil)
  item.tintColor = .black
  navigationItem.rightBarButtonItem = item
  return item
}()
questionIndexItem.title= "\(questionIndex + 1)/" +
"\(questionGroup.questions.count)"

public protocol QuestionViewControllerDelegate: class {
  
  // 1
  func questionViewController(
    _ viewController: QuestionViewController,
    didCancel questionGroup: QuestionGroup,
    at questionIndex: Int)

  // 2
  func questionViewController(
    _ viewController: QuestionViewController,
    didComplete questionGroup: QuestionGroup)
}
public weak var delegate: QuestionViewControllerDelegate?
viewController.delegate = self
// MARK: - QuestionViewControllerDelegate
extension SelectQuestionGroupViewController: QuestionViewControllerDelegate {
  
  public func questionViewController(
    _ viewController: QuestionViewController,
    didCancel questionGroup: QuestionGroup,
    at questionIndex: Int) {

    navigationController?.popToViewController(self,
                                              animated: true)
  }
  
  public func questionViewController(
    _ viewController: QuestionViewController,
    didComplete questionGroup: QuestionGroup) {

    navigationController?.popToViewController(self,
                                              animated: true)
  }
}
public override func viewDidLoad() {
  super.viewDidLoad()
  setupCancelButton()
  showQuestion()
}
private func setupCancelButton() {
  let action = #selector(handleCancelPressed(sender:))
  let image = UIImage(named: "ic_menu")
  navigationItem.leftBarButtonItem =
    UIBarButtonItem(image: image,
                    landscapeImagePhone: nil,
                    style: .plain,
                    target: self,
                    action: action)
}

@objc private func handleCancelPressed(sender: UIBarButtonItem) {
  delegate?.questionViewController(
    self,
    didCancel: questionGroup,
    at: questionIndex)
}

代表?.questionViewController(self,
                                 didComplete: questionGroup)

关键点

您在本章中了解了委派模式,包括如何使用Apple提供的代表以及如何创建自己的代表。以下是您学到的关键点:

有一个技术问题?想记者一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。