🍎

「TSからSwiftへ」#1 Swift Protocolの正体:「形」から「振る舞い」への転換

に公開

はじめに

TypeScript(以下TS)を長年触ってきたエンジニアがSwiftを学び始めると、最初に「似ているようで全く違う」と感じるのが Protocol です。

TSの interface は主にオブジェクトの「形」を定義するものですが、Swiftの Protocol はその名の通り、型が守るべき規約であり、強力な「振る舞い」を付与する武器です。

今回は、TSエンジニアの視点から、Swift Protocolの真髄を3つのポイントで解説します。

「構造的部分型」vs「名義型」

TSの interface は 構造的部分型 です。プロパティの形が合っていれば、明示しなくてもその型として扱われます。

// TS: 形が合っていればOK
interface User { id: string }
const login = (user: User) => { ... }

login({ id: "123", name: "user" });

一方、Swiftは 名義型 です。たとえプロパティが一致していても、明示的にプロトコルに準拠させなければなりません。

/// Swift: 明示的な宣言が必要
protocol User { var id: String { get } }

struct Member: User { let id: String } // : User が必須

func login(user: User) { ... }

なぜこれが必要なのか?
→ それは、大規模な開発において「たまたま形が一致しただけの別物」を混ぜないためです。Swiftは、プログラマの意図をコンパイル時に厳格にチェックします。

Protocol Extension:interfaceを超えたロジックの注入

TSエンジニアがSwiftで最も感動するのが Protocol Extension です。
TSでは interface にロジックを持たせることはできません。共通処理を作るときは Class の継承や Utility 関数を使いますが、Swiftではプロトコル自体にデフォルト実装を書けます。

protocol Logger {
    func log(message: String)
}

// プロトコルを拡張してデフォルトの挙動を定義
extension Logger {
    func log(message: String) {
        print("[Log]: \(message)")
    }
}

struct MyService: Logger {} 
// 実装しなくても、自動的に log() メソッドが使える

これはTSでいうところの Mixin や Abstract Class に近いですが、より安全でクリーンです。これにより、継承ではなく合成によるコードの再利用が可能になります。

Associatedtypes:ジェネリクスのもう一つの姿

TSでは interface Repository<T> のようにジェネリクスを使いますが、Swiftのプロトコルでは associatedtype を使います

protocol Repository {
    associatedtype Item // 関連型
    func fetchAll() -> [Item]
}

struct FoodRepository: Repository {
    typealias Item = Food // ここで具体的な型を決定
    func fetchAll() -> [Food] { ... }
}

なぜ <T> ではないのか?
associatedtype を使うことで、そのプロトコルを使う側が具体的な型を決定する権利を持ちます。これにより、プロトコルを引数や戻り値として使う際、型推論がより強力に働き、コードがスッキリします。

まとめ ー プロトコル指向プログラミング(POP)へ

Swiftの世界では、まず「どんなクラスを作るか」ではなく、「どんな振る舞いが必要か」から考え始めます。

  • TS interface: オブジェクトの形をチェックするフィルター
  • Swift Protocol: 型に能力を授ける設計図

このパラダイムシフトを受け入れると、Swiftでの開発は一気に楽しくなります。

Discussion