Swiftでプロトコルをマスターするたった10のステップ

Swiftでプロトコルを理解し使いこなすためのガイドSwift
この記事は約24分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

プロトコルはSwiftプログラミング言語において非常に重要なコンポーネントです。

プロトコルを理解して使いこなせるかどうかで、プログラムの効率、拡張性、メンテナビリティが大きく変わります。

この記事では、プロトコルの基本的な使い方から応用までを一通り解説します。

サンプルコードも豊富に用意していますので、初心者から中級者まで、Swiftでプロトコルを効率よく使いこなすための手引きとしてお役立てください。

●Swiftとは

SwiftはAppleによって開発されたプログラミング言語です。

Objective-Cに代わる言語として設計され、iOSやmacOSなどのApple製品で広く使用されています。

○Swiftの基本概念

Swiftは静的型付け言語であり、高度な型推論機能を備えています。

これにより、安全かつ効率的なコードを書くことが可能です。

また、Swiftは関数型プログラミングの概念も取り入れており、より短く、より明瞭なコードの記述が可能です。

○Swiftの歴史と特徴

Swiftは2014年にAppleによって初めて公開されました。

Objective-Cよりも読みやすく、安全性が高いとされています。

特にエラーハンドリングが強化されており、null参照の可能性を減らすオプショナル型など、多くの新機能が取り入れられています。

●プロトコルとは

プロトコルはSwiftプログラミングにおいて非常に重要な概念です。

プロトコルは、メソッド、プロパティ、その他の名前付け要件の一覧を定義するもので、この概念によって非常に効率的なコード設計が可能になります。

○プロトコルの基本

Swiftにおけるプロトコルは、特定のメソッドやプロパティが必要であることを示す設計図のようなものです。

このプロトコルを採用(遵守)するクラスや構造体は、プロトコルで定義されたメソッドやプロパティを必ず実装しなければなりません。

// プロトコルの基本的な定義
protocol SimpleProtocol {
    func simpleMethod()
}

このコードではSimpleProtocolという名前のプロトコルを定義しています。

この例ではsimpleMethodというメソッドを持つことが求められています。

○プロトコルの必要性

プロトコルは、コードの再利用性を高めるため、または複数の異なる型に共通のインターフェースを提供するために非常に有用です。

例えば、複数のクラスで同じメソッドを使用する場合、それらのクラスは同一のプロトコルに適合させることで、メソッドの再利用が容易になります。

// SimpleProtocolを遵守するクラス
class SimpleClass: SimpleProtocol {
    func simpleMethod() {
        print("SimpleMethod is called.")
    }
}

このコードではSimpleClassというクラスがSimpleProtocolを遵守しています。

その結果、simpleMethodというメソッドを持つ必要があり、実際にそのメソッドを実装しています。

このメソッドを呼び出すと、コンソールにSimpleMethod is called.と出力されます。

●Swiftでのプロトコルの使い方

プロトコルは、Swiftプログラミング言語の基本的な要素の一つです。

プロトコルは、特定のクラス、構造体、または列挙型が遵守しなければならないメソッドやプロパティの「青写真」を定義します。

この記事では、Swiftでプロトコルをどのように使用するか、その基本から応用までを網羅しています。

具体的なサンプルコードも交えながら解説していきます。

○サンプルコード1:プロトコルの基本的な使い方

最も基本的なプロトコルの使い方を説明します。

ここでは、Animalというプロトコルを定義し、それを遵守するDogCatクラスを作成するサンプルコードを紹介します。

protocol Animal {
    func makeSound() -> String
}

class Dog: Animal {
    func makeSound() -> String {
        return "ワンワン"
    }
}

class Cat: Animal {
    func makeSound() -> String {
        return "ニャー"
    }
}

このコードではAnimalプロトコルを使ってmakeSoundというメソッドを定義しています。

この例ではDogクラスとCatクラスがAnimalプロトコルに遵守してmakeSoundメソッドを実装しています。

コードを実行すると、各動物クラスのmakeSoundメソッドがそれぞれの動物の鳴き声を文字列で返します。

つまり、「ワンワン」や「ニャー」といった結果が得られます。

○サンプルコード2:プロトコルと構造体

プロトコルはクラスだけでなく、構造体にも適用できます。

ここでは、Printableプロトコルを定義し、それを遵守するBook構造体を作成するサンプルコードを紹介します。

protocol Printable {
    var description: String { get }
}

struct Book: Printable {
    var title: String
    var author: String

    var description: String {
        return "\(title) by \(author)"
    }
}

このコードではPrintableプロトコル内でdescriptionという読み取り専用のプロパティを定義しています。

Book構造体はこのプロトコルに遵守し、descriptionプロパティを実装しています。

このコードを実行すると、Book構造体のインスタンスが持つdescriptionプロパティは、例えば「Swift Programming by Apple」といった書名と著者名を結合した形の文字列を返します。

○サンプルコード3:プロトコルとクラス

Swiftではプロトコルを用いることでクラスに一定の規約や振る舞いを与えることができます。

プロトコルとクラスの組み合わせは、特に大規模なプロジェクトや複数人での開発において、一貫性を保ちつつ柔軟にコードを管理する上で非常に重要です。

このコードではAnimalプロトコルを定義し、それをCatDogクラスが採用しています。

この例では、Animalプロトコルで定義されたsoundメソッドを、CatクラスとDogクラスでオーバーライドして具体的な動作を実装しています。

protocol Animal {
    func sound() -> String
}

class Cat: Animal {
    func sound() -> String {
        return "にゃーん"
    }
}

class Dog: Animal {
    func sound() -> String {
        return "わんわん"
    }
}

let myCat: Animal = Cat()
print(myCat.sound()) // にゃーん

let myDog: Animal = Dog()
print(myDog.sound()) // わんわん

この例のようにプロトコルを採用したクラスは、プロトコルで定義されたメソッドやプロパティを必ず実装する必要があります。

これによって、Animal型の変数に代入されたインスタンス(myCat, myDog)が確実にsoundメソッドを持つことが保証されます。

このコードを実行すると、それぞれの動物の鳴き声が出力されます。

具体的には、myCat.sound()"にゃーん"を、myDog.sound()"わんわん"を出力します。

○サンプルコード4:プロトコルと列挙型

Swiftでは、列挙型(enum)もプロトコルに適合(conform)させることができます。

列挙型にプロトコルを適用することで、より柔軟なコード設計が可能になります。

まずは、シンプルな例から見ていきましょう。

protocol Describable {
    func describe() -> String
}

enum Beverage: Describable {
    case coffee, tea, juice

    func describe() -> String {
        switch self {
        case .coffee:
            return "カフェインが含まれています"
        case .tea:
            return "リラックスできる飲み物です"
        case .juice:
            return "フレッシュな果物から作られました"
        }
    }
}

let myDrink = Beverage.coffee
print(myDrink.describe())

このコードでは、Describableという名前のプロトコルを定義しています。

このプロトコルにはdescribeというメソッドが定義されており、それを適用したBeverageという列挙型があります。

列挙型の各ケース(coffee、tea、juice)に対してdescribeメソッドを用いることで、その飲み物に関する説明を取得できます。

コードを実行すると、"カフェインが含まれています"と表示されます。

これはlet myDrink = Beverage.coffeeによって選択されたBeverage型の.coffeeケースに対する説明です。

●プロトコルの詳細な使い方

プロトコルはSwiftで非常に重要な要素の一つです。

しかし、初心者から中級者まで、多くの人がその実用的な使い方について十分に理解しているわけではありません。

ここでは、Swiftにおけるプロトコルの詳細な使い方について、サンプルコードを交えて解説します。

○サンプルコード5:プロトコルでのメソッド定義

プロトコルではメソッドも定義できます。

具体的な実装は持たず、形だけを定義することになります。

protocol Speakable {
    func speak()
}

このコードではSpeakableという名前のプロトコルを定義しています。

この例ではspeakというメソッドを持っていることが要求されます。

ここでは、このプロトコルに準拠したDogクラスの例を紹介します。

class Dog: Speakable {
    func speak() {
        print("ワンワン")
    }
}

このDogクラスはspeakメソッドを実装しているため、Speakableプロトコルに適合します。

このようにコードを書き、Dogクラスのインスタンスを生成してspeakメソッドを呼び出すと、「ワンワン」と出力されます。

○サンプルコード6:プロトコルでのプロパティ定義

プロトコルでプロパティも定義できますが、これも実装は持たない形になります。

プロパティにはgetsetキーワードを使って読み書きの可否を明示します。

protocol Named {
    var name: String { get set }
}

このコードではNamedというプロトコル内でnameという文字列型のプロパティを定義しています。

get setとありますので、このプロパティは読み書きが可能です。

ここでは、このプロトコルを実装したPersonクラスを見ていきましょう。

class Person: Named {
    var name: String
    init(name: String) {
        self.name = name
    }
}

このPersonクラスはnameプロパティを持っているので、Namedプロトコルに適合します。

○サンプルコード7:プロトコル継承

Swiftでプロトコルを活用する上で、理解しておくべき重要な概念のひとつが「プロトコル継承」です。

一言で言えば、プロトコル継承とは、あるプロトコルが別のプロトコルの特性やメソッド、プロパティを引き継ぐことです。

この仕組みを使うと、共通の振る舞いや特性を複数のプロトコルで共有できるようになります。

ここでは、プロトコル継承を実現するSwiftのサンプルコードを紹介します。

protocol Vehicle {
    func start()
    func stop()
}

protocol Motorized: Vehicle {
    var horsepower: Int { get }
    func revEngine()
}

class Car: Motorized {
    var horsepower: Int = 150

    func start() {
        print("Car started.")
    }

    func stop() {
        print("Car stopped.")
    }

    func revEngine() {
        print("Revving engine!")
    }
}

このコードでは、Vehicleというプロトコルを定義して、startstopというメソッドを宣言しています。

次に、Motorizedという新しいプロトコルを作成し、Vehicleプロトコルを継承しています。

Motorizedプロトコルでは、horsepowerプロパティとrevEngineメソッドが新たに追加されています。

CarクラスはMotorizedプロトコルに適合しており、horsepowerプロパティとstartstoprevEngineメソッドを実装しています。

このコードを実行すると、Carクラスのインスタンスを作成し、それらのメソッドを呼び出すことができます。

let myCar = Car()
myCar.start() // 出力:Car started.
myCar.revEngine() // 出力:Revving engine!
myCar.stop() // 出力:Car stopped.

myCarオブジェクトを使ってstartrevEnginestopメソッドを呼び出すと、それぞれのメソッドが実行され、「Car started.」「Revving engine!」「Car stopped.」と出力されます。

○サンプルコード8:プロトコル合成

Swiftでは、一つの型が複数のプロトコルに準拠する必要がある場合に、プロトコル合成を使ってこれを実現します。

ここでは、プロトコル合成の仕組みと、それを活用した具体的なコードについて解説します。

□プロトコル合成の基本

Swiftのプロトコル合成では、& オペレータを使用して複数のプロトコルを組み合わせます。

protocol A {
    func doSomethingA()
}

protocol B {
    func doSomethingB()
}

typealias AB = A & B

このコードではABという二つのプロトコルを定義しています。

そして、typealiasを用いてこれらを組み合わせ、新たな型ABを作成しています。

□具体的なサンプルコード

下記のサンプルコードでは、RunnableSwimmableという二つのプロトコルを定義し、それらを合成してAmphibianという新たな型を作成します。

// プロトコルRunnableとSwimmableの定義
protocol Runnable {
    func run()
}

protocol Swimmable {
    func swim()
}

// RunnableとSwimmableを合成してAmphibianを定義
typealias Amphibian = Runnable & Swimmable

// Amphibianプロトコルに準拠したFrogクラス
class Frog: Amphibian {
    func run() {
        print("Frog is running")
    }
    func swim() {
        print("Frog is swimming")
    }
}

// 使用例
let aFrog = Frog()
aFrog.run()  // "Frog is running"と出力される
aFrog.swim() // "Frog is swimming"と出力される

この例では、Runnableプロトコルでrunメソッド、Swimmableプロトコルでswimメソッドを定義しています。

そして、これらを合成してAmphibianという新たなプロトコルを作成しています。最後に、FrogクラスがAmphibianプロトコルに準拠していることを確認しています。

このコードを実行すると、aFrog.run()は”Frog is running”、aFrog.swim()は”Frog is swimming”と出力されます。

●プロトコルの詳細な対処法

Swiftでプロトコルを使用する際には、いくつかの具体的な対処法が必要です。

ここではエラーハンドリングの仕組みから、ジェネリクスを活用する方法まで詳細に解説します。

○エラーハンドリングとプロトコル

Swiftではプロトコル内でエラーハンドリングを行うことができます。

具体的にはthrowsキーワードを使って、メソッドがエラーを投げる可能性があることを示すことができます。

このコードではExampleProtocolという名前のプロトコルを作成し、performTaskというエラーを投げる可能性のあるメソッドを定義しています。

この例ではperformTaskメソッドがエラーExampleErrorを投げる可能性があることを表しています。

enum ExampleError: Error {
    case someError
}

protocol ExampleProtocol {
    func performTask() throws
}

struct ExampleStruct: ExampleProtocol {
    func performTask() throws {
        // ロジック処理
        throw ExampleError.someError
    }
}

このコードを実行すると、ExampleError.someErrorが投げられます。

そのため、このメソッドを呼び出す際にはdo-catchブロックを使ってエラーハンドリングをする必要があります。

○プロトコルとジェネリクス

Swiftのジェネリクスはプロトコルと非常に相性が良いです。

ジェネリクスを使うと、型に依存しない柔軟なコードを書くことができます。

このコードではGenericProtocolという名前のプロトコルを作成し、Tというジェネリクスを使っています。

この例ではitemプロパティがT型であること、そしてprintItemメソッドがT型の引数を取ることを示しています。

protocol GenericProtocol {
    associatedtype T
    var item: T { get }
    func printItem(item: T)
}

struct GenericStruct<T>: GenericProtocol {
    var item: T

    func printItem(item: T) {
        print(item)
    }
}

このコードを実行すると、GenericStructのインスタンスを作成し、任意の型でitemプロパティとprintItemメソッドを使用することができます。

●プロトコルの詳細な注意点

Swiftでプロトコルを使う際に留意すべき点は多くあります。

ここでは、特に重要な注意点とその対処法を、サンプルコードとともに詳しく解説します。

○継承と遵守の違い

プロトコルには「継承」と「遵守」の二つの側面がありますが、これらは別の概念です。

継承は、既存のプロトコルに新しい機能を追加する手法であり、遵守はプロトコルが定義するメソッドやプロパティを実装することを指します。

例えば、下記のサンプルコードではAnimalプロトコルを継承してMammalプロトコルを作成し、DogクラスがMammalプロトコルに遵守しています。

// Animalプロトコル
protocol Animal {
    func eat()
}

// Animalプロトコルを継承したMammalプロトコル
protocol Mammal: Animal {
    func run()
}

// Mammalプロトコルに遵守するDogクラス
class Dog: Mammal {
    func eat() {
        print("Dog is eating.")
    }

    func run() {
        print("Dog is running.")
    }
}

このコードではAnimalプロトコルを使って基本的なeatメソッドを定義し、それをMammalプロトコルで継承しています。

そして、DogクラスはMammalプロトコルに遵守してeatrunメソッドを実装しています。

○プロトコルでの制約

プロトコルでは、型やメソッドに対して制約を加えることが可能です。

これにより、プロトコルを遵守する型が持つべき特性や挙動を明示的にできます。

下記のサンプルコードでは、Comparableプロトコルが提供する<オペレータに制約を加え、自作のPerson構造体でこれに遵守しています。

struct Person: Comparable {
    let age: Int

    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.age < rhs.age
    }
}

このコードではPerson構造体にComparableプロトコルに遵守しています。

<オペレータを使用して、ageプロパティを基にPerson型のインスタンスを比較可能にしています。

制約を設けることで、より堅牢なコードを作成することが可能です。

例えば、Comparableプロトコルに遵守することで、Person型の配列を簡単にソートすることができます。

●プロトコルの詳細なカスタマイズ

プロトコルをSwiftで効率よく活用するためには、基本的な使い方だけでなく、そのカスタマイズ方法についても理解することが重要です。

ここでは、プロトコルの詳細なカスタマイズ方法を、サンプルコードを交えて説明します。

○サンプルコード9:プロトコルと拡張(Extensions)

拡張(Extensions)を使用すると、プロトコルを更に柔軟に使えるようになります。

拡張を用いてメソッドやプロパティを追加することができます。

protocol Greeting {
    func sayHello() -> String
}

extension Greeting {
    func sayHello() -> String {
        return "Hello, World!"
    }
}

struct Person: Greeting {
    // sayHelloメソッドは既に拡張で定義済みなので、ここでは何も書かなくてもよい
}

このコードでは、Greetingというプロトコルを定義し、sayHelloというメソッドを宣言しています。

その後、拡張を使用してsayHelloメソッドのデフォルト実装を提供しています。

この例では、Person構造体がGreetingプロトコルに準拠しているので、sayHelloメソッドは既に実装済みです。

このコードを実行すると、Person構造体のインスタンスでsayHelloメソッドを呼び出すと、”Hello, World!”が返されます。

○サンプルコード10:プロトコルとオプショナル要素

Swiftのプロトコルにはオプショナルな要素を持たせることができます。

これは、特定のメソッドやプロパティの実装が必須ではない場合に便利です。

@objc protocol OptionalGreeting {
    @objc optional func sayMorning() -> String
}

class Human: OptionalGreeting {
    // sayMorningメソッドはオプショナルなので実装しなくてもよい
}

このコードでは、OptionalGreetingというプロトコルに、オプショナルなsayMorningメソッドが定義されています。

この例では、HumanクラスはsayMorningメソッドを実装していませんが、OptionalGreetingプロトコルには適合しています。

このコードを実行すると、HumanクラスのインスタンスでsayMorningメソッドを呼び出すと、何も返されません。

●プロトコルの応用例

プロトコルはSwiftで非常に多くの場面で活用されています。

ここでは、プロトコルがどのように応用されるのか、そしてそれがどれほど強力なのかを理解するための実例をいくつか紹介します。

○サンプルコード11:プロトコルを使ったデザインパターン

デザインパターンはプログラム設計における一般的な問題解決の方法ですが、Swiftでのプロトコルはこのようなパターンを実装する上で非常に役立ちます。

具体的には、”Observer Pattern(オブザーバーパターン)”においてプロトコルを活用してみましょう。

// Observerプロトコルを定義
protocol Observer {
    func update(message: String)
}

// ConcreteObserverクラスを定義
class ConcreteObserver: Observer {
    func update(message: String) {
        print("通知を受けました: \(message)")
    }
}

// Subjectクラスを定義
class Subject {
    private var observers: [Observer] = []

    func addObserver(observer: Observer) {
        observers.append(observer)
    }

    func notifyObservers(message: String) {
        for observer in observers {
            observer.update(message: message)
        }
    }
}

このコードでは、Observerプロトコルを使って、通知を受け取るオブジェクトを抽象化しています。

この例では、ConcreteObserverクラスがObserverプロトコルに適合しており、updateメソッドを具体的に実装しています。

また、SubjectクラスがObserverオブジェクトを管理し、通知が必要になった際にはnotifyObserversメソッドで全てのObserverに通知を送っています。

このコードを実行すると、次のような出力が得られます。

let observer1 = ConcreteObserver()
let observer2 = ConcreteObserver()
let subject = Subject()

subject.addObserver(observer: observer1)
subject.addObserver(observer: observer2)

subject.notifyObservers(message: "新しいデータがあります")

実行すると、「通知を受けました: 新しいデータがあります」というメッセージが2回表示され、observer1とobserver2の両方が通知を受け取っていることが確認できます。

○サンプルコード12:プロトコルと関数型プログラミング

Swiftは関数型プログラミングもサポートしており、プロトコルは関数型の概念とも相性が良いです。

例として、”Monad”という関数型プログラミングにおける概念をプロトコルで表現してみましょう。

// Monadプロトコルを定義
protocol Monad {
    associatedtype Element

    func bind<B>(_ transform: (Element) -> B?) -> B?
}

// OptionalはMonadプロトコルに適合
extension Optional: Monad {
    func bind<B>(_ transform: (Wrapped) -> B?) -> B? {
        switch self {
        case .some(let value):
            return transform(value)
        case .none:
            return nil
        }
    }
}

このコードでは、Monadプロトコルを定義しています。

そしてSwiftのOptional型がこのMonadプロトコルに適合するように拡張しています。

この例では、bindメソッドを使ってOptionalな値を変換しています。

このコードを実行すると、次のような結果が得られます。

let optionalValue: Int? = 5
let result = optionalValue.bind { $0 * 2 }

実行すると、resultにはOptional(10)が格納されます。

これにより、Optional値を安全に変換することができます。

まとめ

本記事ではSwiftのプロトコルについて詳しく解説しました。

基本的な使い方から、より高度な応用例、注意点、対処法に至るまで、多角的にプロトコルの使い方を考察しました。

特に応用例では、デザインパターンと関数型プログラミングの文脈でのプロトコルの有用性を探りました。

プロトコルはSwiftでのプログラミングにおいて、コードの再利用、拡張、メンテナンスを容易にする強力なツールです。

しかし、その力を最大限に活かすためには、その特性と制約をしっかりと理解する必要があります。

この記事が、Swiftでプロトコルをより効率よく、そして効果的に使用するための参考になれば幸いです。