[Swift]「プロトコル」

iOSアプリ開発において、効率的かつ柔軟なコード設計が求められています。そこで、プロトコルという強力な概念が登場します。

本記事では、プロトコルについて基本から解説し、具体的な使い方や活用法までを網羅的にご紹介します。

目次

プロトコルの基礎

基本的な概念

プロトコルは、コードの再利用性柔軟性を高めるための強力なツールです。

  • コードの再利用性が向上
    異なるクラスが同じプロトコルを満たす場合、それらのクラスは同じメソッドプロパティを使用することができます。これにより、一つのプロトコルを定義するだけで複数のクラスがそれを活用できるようになります。
  • コードの柔軟性が向上
    プロトコルを使用することで、異なるクラスが同じプロトコルを満たすことができるため、クラス間の疎結合性が高まります。これにより、コードの変更や拡張が容易になります。

疎結合性の高いとは、各クラスが互いに独立して存在し、他のクラスの内部の詳細に依存せずに機能すること。

基本的な使い方

プロトコルは、クラスや構造体、列挙型などの型が準拠(コンフォーム)することができる仕様です。
プロトコルを定義することで、特定のメソッドプロパティの実装を要求することができます。

プロトコルの宣言採用

まず、一般的なプロトコルの宣言採用方法について見ていきましょう。

パターン
プロトコルの定義
protocol MyProtocol {
    //  要件定義
}
  • プロトコル MyProtocol を定義しています。
パターン
プロトコルの採用(構造体・クラスの場合)
struct MyStruct: MyProtocol {
// 具体的な処理内容 }

class MyClass: MyProtocol {
// 具体的な処理内容 }
  • プロトコル MyProtocol を採用した構造体 MyStruct とクラス MyClass を定義しています。
  • プロトコルを採用すると、そのプロトコルで要求されているメソッドやプロパティを必ず実装しなければなりません。
パターン
複数のプロトコルを採用(構造体の場合)
struct MyStruct: FirstProtocol
    , SecondProtocol {
// 具体的な処理内容 }
  • 構造体 MyStruct が複数のプロトコルである FirstProtocolSecondProtocol を同時に採用しています。
  • これにより、MyStruct は両方のプロトコルの要求を満たすメソッドやプロパティを実装する必要があります。
パターン
複数のプロトコルを採用(クラスの場合)
class MyClass: SuperClass
    , FirstProtocol, SecondProtocol {
// 具体的な処理内容 }
  • クラス MyClassSuperClass という他のクラスを継承しつつ、FirstProtocolSecondProtocol の両方のプロトコルを採用しています。
  • このように複数のプロトコルを採用することで、異なるプロトコルの要求を満たすメソッドやプロパティを実装することができます。

プロトコルの実装

次に、以下のようなDrawableプロトコルの実装を考えてみましょう。

STEP
プロトコルの定義
protocol Drawable {
    func draw()
}
  1. Drawable という名前のプロトコルが定義されています。
  2. このプロトコルには draw() というメソッドが含まれています。
  3. このプロトコルを採用するクラスは、必ず draw() メソッドを実装する必要があります。
STEP
クラスの定義

次に、このプロトコルを満たす具体的なクラスを作成してみましょう。例えば、円を描画するCircleクラスと長方形を描画するRectangleクラスを考えます。

class Circle: Drawable {
    func draw() {
        print("円を描画します")
    }
}

class Rectangle: Drawable {
    func draw() {
        print("長方形を描画します")
    }
}
  • Circle クラスは、Drawable プロトコルを採用しています。
  • draw() メソッドが呼ばれると、”円を描画します” というメッセージが表示されます。
  • Rectangle クラスも同様に、Drawable プロトコルを採用しています。
  • draw() メソッドが呼ばれると “長方形を描画します” というメッセージが表示されます。
STEP
関数の定義

では、実際にこれらのクラスを使って柔軟なコードを実現してみましょう。以下のような関数を考えてみます。

func drawShape(_ shape: Drawable) {
    shape.draw()
}
  • drawShape という関数は、引数として Drawable プロトコルを採用するオブジェクトを受け取り、そのオブジェクトの draw() メソッドを呼び出して描画を行います。
  • この関数を使うことで、CircleRectangle インスタンスなど、Drawable プロトコルを採用したどのクラスのオブジェクトでも描画が可能になります。
STEP
インスタンスの生成

例えば、以下のように関数を呼び出すことができます。

let circle = Circle()
let rectangle = Rectangle()

drawShape(circle) 
//出力結果: "円を描画します"

drawShape(rectangle) 
//出力結果: "長方形を描画します"
  • 最後に、Circle クラスと Rectangle クラスのインスタンスを生成し、それぞれ drawShape 関数に渡して描画を行っています。
  • それぞれのインスタンスは Drawable プロトコルを採用しているため、drawShape 関数内で適切な描画が行われます。

プロトコルの継承と拡張

プロトコルの継承

プロトコルを継承することで、既存のプロトコルの機能を引き継ぎながら、さらに新たな機能を追加することができます。

プロトコルの継承
protocol Loggable {
    func log()
}

protocol Alertable: Loggable {
    func showAlert()
}
  • AlertableプロトコルLoggableプロトコル継承しています。
  • これにより、Alertableを満たすクラスは、Loggableプロトコルの機能であるlog()メソッドに加えて、showAlert()メソッドを実装する必要があります。

プロトコル拡張

プロトコル拡張は、既存のプロトコルに対して追加の機能を提供する手法です。

STEP
プロトコルの定義
protocol Loggable {
    func log()
}
  • Loggable という名前のプロトコルが定義されています。
  • このプロトコルには log() というメソッドが含まれており、採用するクラスや構造体は必ずこのメソッドを実装しなければなりません。
STEP
プロトコルのデフォルト実装
extension Loggable {
    func log() {
        print("Default log implementation")
    }
}
  • Loggable プロトコルには、デフォルトの log() 実装が extension を通じて追加されています。
  • これは、プロトコルを採用する型が特別な実装を提供しない場合に使用されます。
  • 具体的には、採用する型が log() を実装していない場合に、このデフォルトの実装が使用されます。
STEP
構造体の定義
struct MyStruct: Loggable {
    // プロトコルに準拠する型でも追加の実装をすることができる
    func log() {
        print("Custom log implementation")
    }
}
  • MyStruct という名前の構造体が定義されています。
  • この構造体は Loggable プロトコルを採用しており、そのため log() メソッドを実装しなければなりません。
  • しかし、この実装はデフォルトのものではなく、独自の実装で “Custom log implementation” というメッセージを表示する内容です。

プロトコル拡張利点は、既存の型に手を加えずに新たな機能を追加できることです。これにより、既存のコードを変更することなく、機能の拡張や修正が可能となります。また、プロトコル拡張はコードの再利用性を向上させることができるため、開発効率を向上させることもできます。

プロトコル指向のデザインパターン

iOS開発におけるプロトコル指向のデザインパターンについて解説します。

デリゲーションパターン

デリゲーションパターンは、オブジェクト間の通信を行う際によく使用されます。
プロトコルを定義し、そのプロトコルを満たすオブジェクトをデリゲートとして指定することで、イベントやデータの受け渡しを行います。

STEP
プロトコルの定義
protocol DataDelegate {
    func didReceiveData(data: Any)
}
  • DataDelegate という名前のプロトコルが定義されています。
  • このプロトコルには、didReceiveData(data:) というメソッドが含まれています。
  • このメソッドは、データを受信した後の処理を通知するために使用されます。
STEP
クラスの定義(delegate司令塔)
class DataManager {
    var delegate: DataDelegate?
    
    func fetchData() {
        let data = "Sample Data"
        delegate?.didReceiveData(data: data)
    }
}
  • DataManager クラスは、データの取得とその後の処理を行います。
  • delegate プロパティは DataDelegate プロトコルを採用する型(ここでは ViewController クラス)のインスタンスを保持します。
  • fetchData() メソッド内では、データを取得し、その後 delegatedidReceiveData(data:) メソッドを呼び出してデータを通知しますdelegate司令塔)。
var delegate: DataDelegate?
  1. delegate プロパティは、DataDelegate プロトコルを採用する型(クラスや構造体など)のインスタンスを保持するための変数です。
  2. この変数には、DataDelegate プロトコルに準拠した型のインスタンスが代入されます。
  3. 具体的には、ここでは ViewController クラスが DataDelegate プロトコルを採用しているため、DataManagerdelegate プロパティには ViewController クラスのインスタンスを代入することができます。
STEP
クラスの定義(delegate受信者)
class ViewController: UIViewController, DataDelegate {
    let dataManager = DataManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        dataManager.delegate = self
        dataManager.fetchData()
    }
    
    func didReceiveData(data: Any) {
        print("Received data: \(data)")
    }
}
  • ViewController クラスは、UIViewController を継承しながら、DataDelegate プロトコルも採用しています。
  • dataManager インスタンスは DataManager クラスのオブジェクトを持ち、その delegate に自身を設定していますdelegate受信者)。
  • viewDidLoad() メソッド内では、dataManagerfetchData() メソッドを呼び出し、データの取得と通知が行われます。
  • また、didReceiveData(data:) メソッドでは、受け取ったデータを処理しています。

ファクトリーパターン

ファクトリーパターンは、オブジェクトの生成を専門に行うクラス(ファクトリー)を使用して、柔軟なオブジェクトの作成を実現します。
プロトコルを使用することで、異なるクラスインスタンスを統一的に生成することができます。

STEP
プロトコルの定義
protocol Product {
    func use()
}
  • Product という名前のプロトコルが定義されています。
  • このプロトコルには、use() というメソッドが含まれています。
  • このメソッドは、生成されたオブジェクトがどのように使用されるかを示すために使用されます。
STEP
クラスの定義
class ConcreteProductA: Product {
    func use() {
        print("Using Product A")
    }
}
class ConcreteProductB: Product {
    func use() {
        print("Using Product B")
    }
}
  • ConcreteProductA クラスは、Product プロトコルを採用しています。
  • use() メソッドの実装が行われており、このメソッドが呼ばれると “Using Product A” というメッセージが表示されます。
  • 同様に、ConcreteProductB クラスも Product プロトコルを採用しています。
  • use() メソッドの実装が行われており、このメソッドが呼ばれると “Using Product B” というメッセージが表示されます。
STEP
クラスの定義(オブジェクト生成)
class ProductFactory {
    static func createProduct() -> Product {
        if shouldCreateProductA {
            return ConcreteProductA()
        } else {
            return ConcreteProductB()
        }
    }
}
  • ProductFactory クラスは、オブジェクトの生成を担当します。
  • createProduct() メソッドは、プロトコル Product を満たすクラスのインスタンスを生成します。
  • ここで shouldCreateProductA に応じて、ConcreteProductA クラスのインスタンスか ConcreteProductB クラスのインスタンスを返します。
if shouldCreateProductA {
  • shouldCreateProductA の値によって、生成されるインスタンスの種類が切り替わります。
  • true なら ConcreteProductA のインスタンス、
  • false なら ConcreteProductB のインスタンスが生成される。
STEP
インスタンスの生成
let product = ProductFactory.createProduct()
  • 最後に、ProductFactorycreateProduct() メソッドを呼び出して、プロトコル Product を満たすクラスのインスタンスを生成しています。
  • product には、生成されたインスタンスが代入されます。

この記事が気に入ったら
いいね または フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次