imageBlog

كتب بواسطة

Delegation Pattern in Swift

Delegation Pattern in Swift

The delegation pattern enables an object to use another “helper” object to provide data or perform a task rather than do the task itself. This pattern has three parts:

·       An object needing a delegate, also known as the delegating object. It’s the object that has a delegate. The delegate is usually held as a weak property to avoid a retain cycle where the delegating object retains the delegate, which retains the delegating object.

·       A delegate protocol, which defines the methods a delegate may or should implement.

·       A delegate, which is the helper object that implements the delegate protocol.

By relying on a delegate protocol instead of a concrete object, the implementation is much more flexible: any object that implements the protocol can be used as the delegate!

When should you use it?

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.

Why isn’t there just one protocol, instead of two, in Apple frameworks?

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

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

It’s common for the dataSource and delegate to be set to the same 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.

 

Playground example

Let’s take a look at some code!
Open FundamentalDesignPatterns.xcworkspace in the Starter directory and then

open the Overview page, if it’s not already.
You’ll see that Delegation is listed under Behavioral Patterns. This is because

delegation is all about one object communicating with another object. Click on the Delegation link to open that page.

For the code example, you’ll create a MenuViewController that has a tableView and acts as both the UITableViewDataSource and UITableViewDelegate.

First, create the MenuViewController class by adding the following code directly after Code Example, ignoring any compiler errors for the moment:

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"]

}

Here’s what this does:

1.    In a real app, you’d also need to set the @IBOutlet for the tableView within Interface Builder, or create the table view in code. You can optionally also set the tableView.delegate and tableView.dataSource directly in Interface Builder, or you can do this in code as shown here.

2.    The items will be used as the menu titles displayed on the table view.

As Xcode is likely complaining, you actually need to make MenuViewController conform

to UITableViewDataSource and UITableViewDelegate. Add the following code below the class definition:

// 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....

} }

Both the UITableViewDataSource and UITableViewDelegate are technically delegate protocols: They define methods that a “helper” object must implement.

It’s easy to create your own delegates too. For example, you can create a delegate to be notified whenever a user selects a menu item.

Add the following code below import UIKit:

public protocol MenuViewControllerDelegate: class {
  func menuViewController(
    _ menuViewController: MenuViewController,
    didSelectItemAtIndex index: Int)

}

Next, add the following property right above @IBOutlet var tableView: public weak var delegate: MenuViewControllerDelegate?

The common convention in iOS is to set delegate objects after an object is created. This is exactly what you do here: after MenuViewController is created (however this may happen in the app), it expects that its delegate property will be set.

Lastly, you need to actually inform this delegate whenever the user selects an item.

Replace the // To do next... comment in the UITableViewDelegate extension with the following:

It’s common convention to pass the delegating object, which in this case is the MenuViewController, to each of its delegate method calls. This way, the delegate can use or inspect the caller if needed.

So now you have created your own delegate protocol, to which the MenuViewController delegates when an item in the list is selected. In a real app, this would handle what to do when the item is selected, such as moving to a new screen.

Easy, right?

What should you be careful about?

Delegates are extremely useful, but they can be overused. Be careful about creating too many delegates for an object.

If an object needs several delegates, this may be an indicator that it’s doing too much. Consider breaking up the object’s functionality for specific use cases, instead of one catch-all class.

It’s hard to put a number on how many is too many; there’s no golden rule. However, if you find yourself constantly switching between classes to understand what’s happening, then that’s a sign you have too many. Similarly, if you cannot understand why a certain delegate is useful, then that’s a sign it’s too small, and you’ve split things up too much.

You should also be careful about creating retain cycles. Most often, delegate properties should be weak. If an object must absolutely have a delegate set, consider adding the delegate as an input to the object’s initializer and marking its type as forced unwrapped using ! instead of optional via ?. This will force consumers to set the delegate before using the object.

If you find yourself tempted to create a strong delegate, another design pattern may be better suited for your use case. For example, you might consider using the strategy pattern instead.

Reference: Design Patterns by Tutorials By Joshua Greene & Jay Strawn.

 

ابدأ قصة نجاحك الرقمي الآن