「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