Swiftの他のクラスに処理してもらう時のアレコレ


delegate

処理を移譲する仕組み

protocol MyDelegate: class {
  func tapped(_ button: Button)
}
final class Button: UIView {
  weak var delegate: MyDelegate? = nil

  func touchesEnded() {
    delegate?.tapped(self)
  }
}

大体の場合はプロトコルとクラスがセットになる

class ViewController: UIViewController, MyDelegate {
  @IBOutlet weak var button: Button!

  func viewDidLoad() {
    button.delegate = self
  }

  func tapped(_ button: Button) {
    print("タップされた")
  }
}

このように使う。
delegate?.tapped(self)が呼ばれると、ViewControllerのfunc tapped(_ button: Button)が呼ばれる。
この例ではButtonとViewControllerという2つのクラスが存在しているが、
ButtonからViewControllerのメソッドを呼んで実行していると考えるのが分かりやすいと思う。
これが一番スタンダードなコールバックだと思う。

Blocks

ObjCの時代によく見たやつ

class ViewController: UIViewController {
  @IBOutlet weak var button: Button!

  override func viewDidLoad() {
    super.viewDidLoad()

    button.closure = { (button) in
      print("タップされた")
    }
  }
}

class Button: UIView {
  var closure: ((Button)->Void)? = nil

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    closure?(self)
  }
}

これは要するに、((Button)->Void)という構造の処理を保持して、タップされた時に実行しているという感じ。
便利だけど、強参照・弱参照とか考えないとヤバい感じになりがち。

Notification

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(tapped), name: Notification.Name(rawValue: "tap.action"), object: nil)
  }

  func tapped(_ notification: Notification) {
    print("タップされた")
  }
}

class Button: UIView {
  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "tap.action")))
  }
}

もはやインスタンスがどこにあろうと処理を別の所で出来る強いやつ。
上の例だとボタンが2つある時とかはどっちが押されたかとか判別するのにはNotificationにタグ情報を入れたりしないといけないので骨が折れる。
基本的には一箇所で起きるイベントに対して多くのクラスでアクションが行われる時に使う。
処理も追いにくいのでなるべく使わないでほしいやつ。

RxSwift

class ViewController: UIViewController {
  @IBOutlet weak var button: Button!
  let disposeBag = DisposeBag()

  override func viewDidLoad() {
    super.viewDidLoad()
    button.tapped.subscribe(onNext: {
      print("タップされた")
    }).disposed(by: disposeBag)
  }
}

class Button: UIView {
  let tapped = PublishSubject<Void>()

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    tapped.onNext()
  }
}

Blocksに似てるけど、RxSwiftというライブラリを使った書き方。
RxSwiftによるオペレータの恩恵が受けれる。(スレッドの変更や、リトライなど)

やったらダメなやつ

class ViewController: UIViewController {
  @IBOutlet weak var button: Button!

  override func viewDidLoad() {
    super.viewDidLoad()

    button.viewController = self
  }

  func tapped() {
    print("タップされた")
  }
}

class Button: UIView {
  var viewController: ViewController?

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    viewController?.tapped()
  }
}

ButtonにViewControllerを持つパターン。
お互いにお互いのインスタンスを保持してしまうので、メモリから開放されなくなる。

こういう場合の回避方法

class ViewController: UIViewController {
  @IBOutlet weak var button: Button!

  override func viewDidLoad() {
    super.viewDidLoad()

    button.viewController = self
  }

  func tapped() {
    print("タップされた")
  }
}

class Button: UIView {
  weak var viewController: ViewController?

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    viewController?.tapped()
  }
}

子が親を持つ際にweakにすると、弱参照になるのでViewControllerはButtonに保持されずViewControllerの開放に合わせてButtonも開放される。