【Swift】delegate(デリゲート)とは?使い方とメリット

この記事からわかること

  • Swiftdelegate(デリゲート)とは?
  • 使い方メリット
  • 処理を任せるクラスとは?
  • 処理を任されるクラスとは?
  • デリゲートメソッド

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

Swiftでアプリ開発をしているApp DelegateUITableViewDelegateなどDelegateという単語が度々登場します。「Delegate(デリゲート)」とはどのような仕組みなのかまとめていきたいと思います。

Delegate(デリゲート)とは?

Swiftで登場するDelegate(デリゲートパターン)とはSwift独自のものではなく、オブジェクト指向型のプログラミング言語に活用されるGoFのデザインパターン(設計パターン)の一種です。

おすすめ記事:【GoF】23種類のデザインパターンとは?Swiftでよく使う活用例

delegateとは日本語で「委任/委譲」を意味する英単語でその名前の通り、処理を委任する役割をもっています。

では誰が誰に処理を委任するのかというとクラスからクラス(オブジェクトからオブジェクト)です。オブジェクト指向型のプログラミング言語ではクラスを定義してインスタンスを生成し、オブジェクト単位でのデータ操作が大きなメリットになっています。 このオブジェクト間(クラス間)の処理の受け渡しをdelegateでは簡単かつ再利用しやすくすることができます。

メリット

デリゲートパターンを使用することで処理の実装を委任先のオブジェクトに隠蔽することができます。これにより処理を行うオブジェクトと処理を受け取るオブジェクトの間の関係が疎結合になります。 オブジェクト同士はできるだけ依存を減らし、疎結合にしていくことでオブジェクトの関心領域を明確にし、汎用性を高めることができます。

実装例

例えばボタンが押された時に実行される処理を実装するためにデリゲートパターンは活用されます。

「ボタンクラス」と「デバッグエリアにプリントするクラス」があるとします。ボタンには押されたことを検知しその処理を委任するためのオブジェクトを持たせます。このオブジェクトがデリゲートであり、処理を受け取るオブジェクトがデリゲート先となります。デリゲート先には「デバッグエリアにプリントするクラス」を指定すればボタンが押されたことを検知した時にデバッグエリアにプリントされます。

「ボタンクラス」はボタンを押した時に何が実行されるかを知る必要はありません。同様に「デバッグエリアにプリントするクラス」はどのタイミングで呼ばれるかを知る必要はありません

また委譲先のオブジェクトを切り替えるだけで同じ処理を異なる方法で実行することができます。 この状態こそがお互いの依存度が低く、疎結合に近い状態なのだと思います。

Delegate(デリゲートパターン)とは?

Delegate(デリゲートパターン)の仕組みと実装方法

では実際にSwiftでDelegateを使用して処理を委任するための仕組みを構築していきます。今回は基礎的な実装方法に焦点を当てたいので、意味のないクラスを生成することをご了承ください。

Swiftのデリゲートで重要となるのは以下の3つの存在です。

プロトコルとは

プロトコルとは規約のことであり、Swiftではクラスや構造体を定義する際のルール(構造規格)のことを指します。プロトコルを準拠させたクラスはプロトコルの持つプロパティとメソッドの定義が必須になります。

デリゲートではプロトコルを宣言してその中に任せる処理の枠組みをメソッドとして定義しておきます。このメソッドをデリゲートメソッドと呼びます。

デリゲートメソッドには実際の処理部分は記述せず定義のみを記述しておきます。

処理を任せるクラスとは

処理を任せる側のクラスはデリゲートプロトコルには準拠させずに、「処理を任せるクラス」を保持させるプロパティ(名前は慣習的にdelegate)を定義します。

ここには処理を呼び出すまでの流れや条件分岐などを記述しておき具体的な処理はdelegateプロパティで保持するクラスに任せます。。

処理を任されるクラスとは

処理を実際に実装する部分であり、デリゲートプロトコルに準拠させたクラスとして用意します。

このクラスは最初のdelegateプロパティへの格納以降、「処理を任せるクラス」の中で使用される前提で定義されます。

デリゲートを実装するためには上記の3つを準備しておきます。肝となるのは「処理を任されるクラス」のdelegateプロパティです。

delegateプロパティに格納されている「処理を任されているクラス」の値によって処理を分岐したり、別の挙動をおこなせることが可能になります。

デリゲートの使い方

まずはデリゲートとなるプロトコルを定義します。プロトコルはprotocolで宣言します。

protocol DemoDelegate {
    var price:Double { get set }
    
    //  具体的な処理は記述せず定義のみ
    func printText()
    func discountNumber(_ amount:Double)

}

今回は例として引数は「amount:Double」のみにしましたが、カスタムデリゲートを作る際は第一引数に委譲元のクラスを渡すのが規約のようです。

正しくは「func discountNumber(_ demodelegate:DemoDelegate,_ amount:Double)」なるのかもしれません。

GitHub:The Official raywenderlich.com Swift Style Guide.

処理を任せるクラスを作成する

処理を任せる側のクラス自体にはプロトコルを指定せず、クラスのプロパティ(delegateプロパティ)にプロトコルを準拠させます。これでdelegateプロパティにはプロトコルに則った形式のクラスしか格納できないようになります。格納されなかった時に備えnilを許容できるようにしておきます。また循環参照を避けるためにweakをつけておきます。

class Manager {
    weak var delegate: DemoDelegate? = nil

    func execute() {
        if let d = self.delegate {
            d.printText()
            d.discountNumber(5)
        } else {
            print("インスタンス未格納")
        }
    }
}

ここではdelegateプロパティの値によって処理を分岐させることができます。何かしらのクラスが格納されているかや、格納されているクラスでの識別も可能になります。

処理を任されるクラスを作成する

任されるクラスは定義したプロトコルに準拠させたクラスで宣言し、中にはデリゲートメソッドを実装します。デリゲートメソッドの定義は必須なので記述し忘れるとType 'ManagerA' does not conform to protocol 'DemoDelegate'のようなエラーを吐きます。

class ManagerA: DemoDelegate {
    var price: Double = 200
    
    func printText() {
        print("Hello World")
    }
    func discountNumber(_ amount: Double){
        self.price = (self.price + amount) * 0.5
        print(self.price)
    }
}

デリゲートメソッドを実行してみる

使用する際は以下のようにインスタンス化したManagerA()クラスをdelegateプロパティに格納します。これで

let manager = Manager()
manager.delegate = ManagerA()

manager.execute() // デリゲートメソッド達が実行される
// Hello World
// 102.5

再利用して別の処理を作成してみます。

class ManagerB: DemoDelegate {
    var price: Double = 1000
    
    func printText() {
        print("こんにちは世界")
    }
    func discountNumber(_ amount: Double){
        self.price = (self.price + amount) * 0.2
        print(self.price)
    }
}

作成するのはプロトコルにより定型的に定義されている変更したい処理部分のみそれに至るまでの流れや分岐などは変更しなくてすみます

let manager = Manager()
manager.delegate = ManagerA()

manager.execute() // デリゲートメソッド達が実行される
// こんにちは世界
// 201.0

デリゲートプロパティに格納されているクラスで分岐させる

デリゲートプロパティに格納されているクラスを識別して処理を分岐させることも可能です。

class Manager {
    weak var delegate: DemoDelegate? = nil
    func execute() {
        
        if let d = self.delegate {
            if type(of: d) == ManagerA.self {
                // ManagerAの場合のみ
                d.printText()
            } else if type(of: d) == ManagerB.self {
                // ManagerBの場合のみ
                d.discountNumber(5)
            }
        } else {
            print("インスタンス未格納")
        }
    }
}

デリゲートの使用例

実際にデリゲートが組み込まれている処理を見てみるとわかりやすいかもしれません。

今回は例として「iOSアプリでユーザーの位置情報を取得する処理」を見ていきます。

位置情報を取得するためには地図を表示できるMapKitフレームワークをインポートして位置情報を取得するためのCLLocationManagerクラスを使用可能にする必要があります。

下記のコードがデリゲートが絡んだ「iOSアプリでユーザーの位置情報を取得する処理」です。

import MapKit

// 現在地を取得するためのクラス
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    let manager = CLLocationManager()
    @Published  var region =  MKCoordinateRegion()

    override init() {
        super.init() // スーパクラスのイニシャライザを実行
        manager.delegate = self // 自身をデリゲートプロパティに設定
        manager.requestWhenInUseAuthorization() // 現在地取得の許可を申請
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.distanceFilter = 3 // 更新距離(m)
        manager.startUpdatingLocation()
    }
    
    // デリゲートメソッド
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        locations.last.map {
            let center = CLLocationCoordinate2D(
                latitude: $0.coordinate.latitude,
                longitude: $0.coordinate.longitude)

            region = MKCoordinateRegion(
                center: center,
                latitudinalMeters: 1000.0,
                longitudinalMeters: 1000.0
            )
        }
    }
}

それぞれの役割

今回のデリゲートは「処理を任せるクラス」と「処理を任されるクラス」が同一になっています。

準拠しているCLLocationManagerDelegateプロトコルの定義を見てみるとデリゲートメソッドがたくさん定義されています。メソッドの前にoptionalがついているのはプロトコルですが実装が必須ではないようにするキーワードです。

CLLocationManagerDelegateプロトコルの定義

public protocol CLLocationManagerDelegate : NSObjectProtocol {

    @available(iOS 6.0, *)
    optional func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])

    @available(iOS 3.0, *)
    optional func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading)
          .
          .
          .
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index