ぬけてるエンジニアの備忘録

イケてない情報の掃き溜め。。。

【Swift】delegateを完全克服するためのまとめ

iOSアプリ開発に関わると必ずと言って良いほど耳にするdelegate(デリゲート)。これが分かりにくくて苦しんだ人は多いのではないでしょうか。今までUITableViewとかUIScrollViewを使って何となくdelegate(デリゲート)の実装はやった事はあるけど自作delegate(デリゲート)を実装する時に困った場合なんかに参考にしてみて下さい。



そもそもdelegate(デリゲート)ってなに

The Swift Programming Language (Swift 3.0.1): Protocols

Delegation
Delegation is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type. This design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated. Delegation can be used to respond to a particular action, or to retrieve data from an external source without needing to know the underlying type of that source.


簡単に言うとあるクラスから処理の一部を他のクラスに任せたり、他のクラスへメッセージを送る等の目的でよく使われるデザインパターンです。
デザインパターンはよく聞く言葉ですね。GoFなんかで有名なあれです。


ここからはサンプルを作成して実際の作成方法を記載します。

環境 xcode7.0

まず流れとして

  1. SampleView(通知元)にプロトコルを用意する。
  2. SampleView(通知元)でデリゲートメソッドを呼ぶ(処理をデリゲートインスタンスに委譲する)
  3. ViewController(通知先)でSampleView(通知元)で定義したデリゲートメソッドを実装する。


デリゲートを実装する時に考えることは何を委譲するか(なんの処理を呼ばせるのか)を決めるだけです。ぶっちゃけ誰に委譲するか(どこから呼ぶのか)は考えなくて良いです。

SampleView(通知元クラス)の用意

import UIKit

class SampleView: UIView {

    override func drawRect(rect: CGRect) {
        let button = UIButton()
        button.setTitle("Tap", forState: .Normal)
        button.frame = CGRectMake(0, 0, 50, 50)
        button.backgroundColor = UIColor.redColor()
        button.addTarget(self, action: "tappedButton:", forControlEvents:.TouchUpInside)
        
        self.addSubview(button)
        self.backgroundColor = UIColor.blackColor()
    }
    
    @IBAction func tappedButton(sender: AnyObject) {
        self.backgroundColor = UIColor.greenColor()
    }
}

まずは、protocol(プロトコル)の実装

SampleView(通知元クラス)
import UIKit

// SampleViewDelegate プロトコルを記述
@objc protocol SampleViewDelegate {
    // デリゲートメソッド定義
    func didChangeBackgroundColor(str:String)
}

class SampleView: UIView {
    
    //SampleViewDelegateのインスタンスを宣言
    weak var delegate: SampleViewDelegate?

    override func drawRect(rect: CGRect) {
        let button = UIButton()
        button.setTitle("Tap", forState: .Normal)
        button.frame = CGRectMake(0, 0, 50, 50)
        button.backgroundColor = UIColor.redColor()
        button.addTarget(self, action: "tappedButton:", forControlEvents:.TouchUpInside)
        
        self.addSubview(button)
        self.backgroundColor = UIColor.blackColor()
    }
    
    @IBAction func tappedButton(sender: AnyObject) {
        self.backgroundColor = UIColor.greenColor()
    }
}

通知元クラスでデリゲートメソッドを呼ぶ(処理をデリゲートインスタンスに委譲する)

import UIKit

// SampleViewDelegate プロトコルを記述
@objc protocol SampleViewDelegate {
    // デリゲートメソッド定義
    func didChangeBackgroundColor(str:String)
}

class SampleView: UIView {
    
    //SampleViewDelegateのインスタンスを宣言
    weak var delegate: SampleViewDelegate?

    override func drawRect(rect: CGRect) {
        let button = UIButton()
        button.setTitle("Tap", forState: .Normal)
        button.frame = CGRectMake(0, 0, 50, 50)
        button.backgroundColor = UIColor.redColor()
        button.addTarget(self, action: "tappedButton:", forControlEvents:.TouchUpInside)
        
        self.addSubview(button)
        self.backgroundColor = UIColor.blackColor()
    }
    
    @IBAction func tappedButton(sender: AnyObject) {
        self.backgroundColor = UIColor.greenColor()
        
        // デリゲートメソッドを呼ぶ(処理をデリゲートインスタンスに委譲する)
        self.delegate?.didChangeBackgroundColor("グリーン")
    }   
}

ここまでで通知元クラスの実装は終わり。

通知先クラスで通知元で定義したデリゲートメソッドを実装する。

ViewController(通知先クラス)
import UIKit
// 通知側で定義したプロトコル(SampleViewDelegate)の継承
class ViewController: UIViewController,SampleViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        let view = SampleView(frame: CGRectMake(100,100,100,100))
        // SampleViewがインスタンス化されたタイミングでデリゲートインスタンスにselfをセット
        view.delegate = self
        self.view.addSubview(view)
    }
    
    // SampleView DelegateMethod
    // SampleViewのBackgroundColorが変更された事を通知
    func didChangeBackgroundColor(str:String) {
        print(str);
    }
}

SampleViewのボタンをタップする度にViewControllerdidChangeBackgroundColorが呼ばれているのが分かると思います。
ここまででdelegateの流れは終了です。

おまけ

上記のように修飾子なしでデリゲートメソッドを定義すると、通知先で必ず実装しなければいけないメソッドrequiredになります。通知側でrequiredで定義されたメソッドを通知先で記述しなければエラーになってしまいます。
デリゲートメソッド定義の際にoptional修飾子を記述すると必ずしも実装しなくても良いメソッドにする事ができます。

// SampleViewDelegate プロトコルを記述
@objc protocol SampleViewDelegate {
    // デリゲートメソッド定義
    optional func didChangeBackgroundColor(str:String)
}

class SampleView: UIView {
    
    //SampleViewDelegateのインスタンスを宣言
    weak var delegate: SampleViewDelegate?

    override func drawRect(rect: CGRect) {
        let button = UIButton()
        button.setTitle("Tap", forState: .Normal)
        button.frame = CGRectMake(0, 0, 50, 50)
        button.backgroundColor = UIColor.redColor()
        button.addTarget(self, action: "tappedButton:", forControlEvents:.TouchUpInside)
        
        self.addSubview(button)
        self.backgroundColor = UIColor.blackColor()
    }
    
    @IBAction func tappedButton(sender: AnyObject) {
        self.backgroundColor = UIColor.greenColor()
        
        // デリゲートメソッドを呼ぶ(処理をデリゲートインスタンスに委譲する)
        self.delegate?.didChangeBackgroundColor?("グリーン")
    }
}
import UIKit

class ViewController: UIViewController,SampleViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        let view = SampleView(frame: CGRectMake(100,100,100,100))
        view.delegate = self
        self.view.addSubview(view)
    }
}

optional修飾子を記述すればfunc didChangeBackgroundColor(str:String)を呼ばなくてもエラーにならない。

delegateはobj-cの頃から頻繁に使われていましたが、最近swiftに移行も進んで改めてdelegateを再確認するつもりでまとめてみました。