Chapter 16: Multicast Delegate Pattern

大綱

When should you use it?

  • The multicast delegate pattern is a behavioral pattern that’s a variation on the delegate pattern.

    • It allows you to create one-to-many delegate relationships, instead of one-to-one relationships in a simple delegate.

  • An object needing a delegate, also known as the delegating object, is the object that has one or more delegates.

  • The delegate protocol defines the methods a delegate may or should implement.

  • The delegate(s) are objects that implement the delegate protocol.

  • The multicast delegate is a helper class that holds onto delegates and allows you to notify each delegate whenever a delegate-worthy event happens.

  • When should you use it?

    • Use this pattern to create one-to-many delegate relationships.

      • For example, you can use this pattern to inform multiple objects whenever a change has happened to another object. Each delegate can then update its own state or perform relevant actions in response.

Playground example

  • 目標: 製作multicast delegate

// MARK: - Example
let dispatch = DispatchSystem()
// object needing a delegate
var policeStation: PoliceStation! = PoliceStation()
var fireStation: FireStation! = FireStation()

// The multicast delegate
dispatch.multicastDelegate.addDelegate(policeStation)
dispatch.multicastDelegate.addDelegate(fireStation)

dispatch.multicastDelegate.invokeDelegates {
  $0.notifyFire(at: "Ray's house!")
}

print("")
// Police were notified about a fire at Ray's house!
// Firefighters were notified about a fire at Ray's house!”

fireStation = nil

dispatch.multicastDelegate.invokeDelegates {
  $0.notifyCarCrash(at: "Ray's garage!")
}
// Police were notified about a fire at Ray's house!
  • Delegate Protocol

// MARK: - Delegate Protocol
public protocol EmergencyResponding {
  func notifyFire(at location: String)
  func notifyCarCrash(at location: String)
}
  • Delegates

public class FireStation: EmergencyResponding {

  public func notifyFire(at location: String) {
    print("Firefighters were notified about a fire at "
      + location)
  }

  public func notifyCarCrash(at location: String) {
    print("Firefighters were notified about a car crash at "
      + location)
  }
}

public class PoliceStation: EmergencyResponding {

  public func notifyFire(at location: String) {
    print("Police were notified about a fire at "
      + location)
  }

  public func notifyCarCrash(at location: String) {
    print("Police were notified about a car crash at "
      + location)
  }
}
  • Delegating Object

public class DispatchSystem {
  let multicastDelegate =
    MulticastDelegate<EmergencyResponding>()
}
  • MulticastDelegate: 核心

public class MulticastDelegate<ProtocolType> {

  // MARK: - DelegateWrapper
  private class DelegateWrapper {

    weak var delegate: AnyObject?

    init(_ delegate: AnyObject) {
      self.delegate = delegate
    }
  }

  // MARK: - Instance Properties
  private var delegateWrappers: [DelegateWrapper]

  public var delegates: [ProtocolType] {
    delegateWrappers = delegateWrappers
      .filter { $0.delegate != nil }
    return delegateWrappers.map
      { $0.delegate! } as! [ProtocolType]
  }

  // MARK: - Object Lifecycle
  public init(delegates: [ProtocolType] = []) {
    delegateWrappers = delegates.map {
      DelegateWrapper($0 as AnyObject)
    }
  }

  // MARK: - Delegate Management
  public func addDelegate(_ delegate: ProtocolType) {
    let wrapper = DelegateWrapper(delegate as AnyObject)
    delegateWrappers.append(wrapper)
  }

  public func removeDelegate(_ delegate: ProtocolType) {
    guard let index = delegateWrappers.index(where: {
      $0.delegate === (delegate as AnyObject)
    }) else {
      return
    }
    delegateWrappers.remove(at: index)
  }

  public func invokeDelegates(_ closure: (ProtocolType) -> ()) {
    delegates.forEach { closure($0) }
  }
}

What should you be careful about?

  • This pattern works best for “information only” delegate calls.

  • If delegates need to provide data, this pattern doesn’t work well.

    • That’s because multiple delegates would be asked to provide the data, which could result in duplicated information or wasted processing.

Tutorial project

Key points

  • The multicast delegate pattern allows you to create one-to-many delegate relationships. It involves four types: an object needing a delegate, a delegate protocol, delegates, and a multicast delegate.

  • An object needing a delegate has one or more delegates; the delegate protocol defines the methods a delegate should implement; the delegates implement the delegate protocol; and the multicast delegate is a helper class for holding onto and notifying the delegates.

  • Swift doesn’t provide a multicast delegate object for you. However, it’s easy to implement your own to support this pattern.”

Last updated

Was this helpful?