ProgrammingSwift Middle/Lead

Swiftにおけるプロトコル合成とは何か、実際にどのように機能し、複数の継承やプロトコルの通常の使用の代わりにいつ適用すべきか?

Hintsage AIアシスタントで面接を突破

回答。

プロトコル合成は、Swiftで複数のプロトコルに同時に準拠するタイプを作成できるメカニズムです。これは、クラスに対する複数の継承の代替手段です。

背景

Objective-Cは、クラスではなくプロトコルに対してのみ複数の継承をサポートしていました。Swiftはこの伝統を引き継ぎ、プロトコルとその組み合わせを使用して新しい抽象を構築することに重点を置いています。

問題

プログラマーは、しばしば複数の抽象によって定義される動作を持つタイプを作成する必要があります。複数の継承は必然的に階層の競合を引き起こしますが、Swiftではプロトコルとプロトコル合成によって安全に解決されます。

解決策

Swiftでは、'&'オペレーターを使用してプロトコルを組み合わせることができます。これにより、複数のプロトコルに同時に準拠する必要がある変数や関数のパラメータを作成できます。

コードの例:

protocol Drivable { func drive() } protocol Flyable { func fly() } struct FlyingCar: Drivable, Flyable { func drive() { print("Driving") } func fly() { print("Flying") } } func testVehicle(_ vehicle: Drivable & Flyable) { vehicle.drive() vehicle.fly() } testVehicle(FlyingCar())

主な特徴:

  • '&'オペレーターを使用して、関数のパラメータや変数に複数のプロトコルを組み合わせることができます
  • クラス、構造体、列挙体のいずれにも機能します
  • 中間型を作成することなく必要な動作を明示的に記述できます

誘導的な質問。

プロトコル合成のパラメータに、いずれかのプロトコルのみを実装するオブジェクトを渡すことはできますか?

いいえ、オブジェクトは合成に参加するすべてのプロトコルを実装している必要があります。そうでない場合、コンパイラはエラーを出します。

コードの例:

// struct Car: Drivable {} — testVehicleに渡せません、なぜならfly()が実装されていないから

プロトコル合成はタイプに適用されますか、それとも値だけですか?

プロトコル合成は値(変数、関数のパラメータ)に適用されますが、オブジェクトの型定義には使用されません(例えば、新しい型を「プロトコルの合成」として宣言することはできない — 変数だけです)。

コードの例:

var obj: SomeProtocol & AnotherProtocol // 許可されます // typealias MyType = SomeClass & AnotherProtocol // エラー

&を使ってクラスとプロトコルを組み合わせることはできますか?

はい、ただし1つの制限があります。左側にはクラス型(クラスまたはそのサブクラス)だけが許可され、他はすべてプロトコルでなければなりません。そうでない場合、コンパイラはエラーを出します。

コードの例:

class A {} protocol B {} // func f(obj: A & B) {} // 許可されます // func f(obj: A & AnotherClass & B) {} // エラー!クラス型は1つだけが許可されます

一般的なエラーとアンチパターン

  • クラスの複数の継承として機能することを期待する
  • 十分な一つのプロトコルがあれば必要のない合成を利用する
  • 外部ライブラリやプロトコル合成の使用時に契約を破る

実生活の例

ネガティブケース

プロジェクト内でレイヤー間のデータを転送するためにプロトコル合成を使用するオブジェクトが使用されていますが、実際には単一のプロトコルで十分でした:

func present(item: Displayable & Serializable) { ... }

利点:

  • 柔軟で拡張可能なインターフェイス 欠点:
  • ほとんどの転送されるオブジェクトがすべての機能を必要としない場合に複雑さと混乱が生じる

ポジティブケース

明示的なケースでのみプロトコル合成を使用 — 例えば、CodableとIdentifiableの両方をサポートするオブジェクトを処理する場合:

func save<T: Codable & Identifiable>(_ item: T) { ... }

利点:

  • 型の要求を明確に記述
  • エラーを最小限に抑える 欠点:
  • 関数のシグネチャがやや複雑になる可能性がある