AppleはWWDC 2015で、Swiftが最初のプロトコル指向プログラミング言語であると発表しました(
ビデオセッション「Protocol-Oriented Programming in Swift」 )。
このセッションおよび他の多くのセッション(
Swift in Practice 、
UIKitアプリのプロトコルおよび値指向プログラミング )では、プロトコルの使用の良い例を示していますが、プロトコル指向プログラミングとは正式には定義していません。
プロトコル指向プログラミング(POP)に関する多くの記事がインターネット上にあり、プロトコルの使用例が示されていますが、その中でさえPOPの明確な定義は見つかりませんでした。
プロトコルの使用例を分析し、コードをプロトコル指向と呼ぶことができるように従うべき原則を策定しようとしました。
POPを示すコード例を見てみると、次の言語ツールがPOPで重要な役割を果たしていることがわかります:
protocol 、
extensions 、
constraint 。
彼らが私たちに与える機会を見てみましょう。
プロトコル
プロトコルの使用は、いくつかのシナリオに分けることができます。
タイプとしてのプロトコル
OOPからの
インターフェイスと
コントラクトプログラミングからの
コントラクトは 、概念に似ています。 オブジェクトの機能を説明します。 プロパティのタイプ、関数の結果のタイプ、異種コレクションの要素のタイプとして使用できます。 言語の制限により、関連するタイプまたは自己要件を持つプロトコルはタイプとして使用できません。
パターンタイプとしてのプロトコル
一般化プログラミングの
概念に似てい
ます 。
また、オブジェクトの機能を説明する役割も果たしますが、「型としてのプロトコル」とは異なり、一般化された関数の型要件として使用されます。 関連するタイプが含まれる場合があります。
関連型 -概念モデリング型に関連する補助型( ウィキペディアの定義)。
プロトコルをタイプとして使用する場合、およびタイプの制限としていいえ、さらに-両方のシナリオでプロトコルを使用することが必要な場合がある明確な行はありません。 ユースケースを強調してみることができます:
- アプリケーションの上位層に機能を提供し、依存関係としてコンシューマクラスに渡されるクラス-これらは、サービス、リポジトリ、APIクライアント、ユーザー設定などです。
この場合、プロトコルをタイプとして使用する方が便利です(IOCコンテナーに登録でき、使用せずに使用できます)。このサービスを使用するすべての関数にタイプパラメーターを追加する必要はありません。
- 比較、加算、連結などの数学的操作を記述するプロトコル。 この場合、操作が1つのタイプ(SwiftのIntとStringの両方がEquatableプロトコルに対応しますが、試行する場合)比較演算子はパラメーターが同じ型であることを要求するため、それらが同等であるかどうかをチェックすると、コンパイラーはエラーをスローします。 したがって、この場合、プロトコルはタイプテンプレートとして使用されます。
- 関連付けられた型を持つプロトコルをプライベートプロパティに保存することが必要になる場合がありますが、この場合、プロトコルを型として使用できません。 この問題を解決するには、さまざまな方法があります。たとえば、関連するタイプの使用が特定のタイプに置き換えられる類似のプロトコルを作成します。 型消去受信の使用-この場合、関連する型はAny [YourProtocolName]型の汎用パラメーターに転送されます。 別のオプションは、インスタンス自体ではなく、その機能を保存することです。 または、プロパティに保存されているクロージャーでインスタンスをキャプチャします。
特性としてのプロトコル
特性(タイプ) -実装された機能のセットを提供するエンティティ。 クラス/構造/列挙型のビルディングブロックのセットとして機能します。
特性コンセプトの説明は
ここにあります 。
この概念は、継承を置き換えるように設計されています。 OOPでは、クラスの役割の1つは再利用可能なコードの単位です。 再利用自体は継承を通じて行われます。
同時に、クラスはインスタンスの作成に使用されるため、その機能は完全でなければなりません。 これらの2つのクラスの役割はしばしば競合します。 さらに、各クラスにはクラス階層内の特定の場所があり、コードの再利用の単位は任意の場所に適用できます。 解決策として、より軽いエンティティを使用することが提案されています-コードの再利用の単位としての特性、およびクラスは、特性から継承されたロジックを呼び出すための接続要素の役割を割り当てられます。
Swiftは、プロトコルとプロトコル拡張を通じてこの概念を実装します。 プロトコルに固有の必要な機能を「接続」するには、作成するタイプにこのプロトコルへの通信を追加する必要があります-機能を継承するための基本クラスを作成する必要はありません。
特性にはどのような特性があり、プロトコルとの類似性があります。
- traitは、動作を実装する一連のメソッドを提供します。 -プロトコル拡張を使用して追加されたメソッド。
- traitには、動作を提供するためのパラメーターとして機能する一連のメソッドが必要です。 -プロトコル自体に含まれるメソッド(プロトコル要件);
- 特性は、状態を保持する変数を設定しません。 traitが提供するメソッドは、クラスのフィールドに直接アクセスしません。 -拡張メソッドは、保存されたプロパティタイプに追加できません。 プロトコルは、プロパティが何であるべきかという要件を追加できません-計算または保存されるため、拡張メソッドはデータに直接アクセスできません-プロパティアクセサを介して実装されます。
- クラスと特性は、他の特性で構成できます。 メソッドの競合は明示的に解決する必要があります。 -プロトコルに準拠するクラスを追加でき、プロトコルは他のプロトコルへの継承をサポートします。 競合は、たとえば、特定のタイプにキャストすることで解決できます。
protocol Protocol1 { } protocol Protocol2 { } protocol ComposedProtocol: Protocol1, Protocol2 { } extension Protocol1 { func doWork() { print("Protocol1 method") } } extension Protocol2 { func doWork() { print("Protocol1 method") } } extension ComposedProtocol { func combinedWork() { (self as Protocol1).doWork() (self as Protocol2).doWork() print("ComposedProtocol method") } }
- 特性を追加しても、クラスのセマンティクスには影響しません。特性からのメソッドを使用するか、クラスで直接定義されたメソッドを使用するかに違いはありません。 -それはプロトコルに当てはまります-コードを見ると、メソッドがどこで定義されているかを判断できません-プロトコル拡張またはプロトコルに対応するタイプ;
- 合成特性は、特性のセマンティクスに影響を与えません-複合特性は、同じメソッドを含む「フラット」特性と同等です。 -foo()メソッドでFooプロトコルを使用し、bar()メソッドでBarプロトコルから、baz()メソッドでBazから継承されますが、foo()、bar()、baz()の3つのメソッドでプロトコルを使用するのと違いはありません。
ご覧のとおり、プロトコルはSwiftが登場するずっと前に説明された特性の概念と完全に一致しています。
マーカーとしてのプロトコル
タイプの「属性」として使用されます。この場合、プロトコルにはメソッドが含まれていません。 例は、CoreDataのNSFetchRequestResultです。 彼は、NSNumber、NSDictionary、NSManagedObject、NSManagedObjectIDをマークしました。 この場合のプロトコルは、クラスの機能については説明していませんが、CoreDataがこれらのクラスをクエリ結果のタイプとしてサポートしているという事実を説明しています。 NSFetchRequestResultプロトコルでマークされていないタイプを結果として指定すると、アセンブリ段階でエラーが発生します。
プロトコルマーカーの存在の確認は、ロジックの分岐にも使用できます。
if object is HighPrioritized { ... }
拡張機能
拡張機能は、既存のタイプまたはプロトコルに機能を追加できる言語ツールです。
拡張機能を使用すると、次のことができます。
- プロトコルにメソッドを追加します-このメソッドは、このプロトコルに対応する型内とそのコンシューマーの両方で使用するために(スコープ内で)利用可能になります。
- クラス/構造/列挙プロトコルのコンプライアンスを追加します。 同時に、これらのタイプのコードにアクセスする必要はなく、サードパーティのライブラリに含めることができます。 この機能は、遡及モデリングと呼ばれます。
プロトコルを別のプロトコルに適合させることはできません。 これが可能で、P2プロトコルに対応するP1プロトコルがある場合、P1プロトコルに対応するすべてのタイプは、P2プロトコルにも自動的に対応します。 この問題の回避策として、次のトリックを使用できます:P1プロトコルの拡張機能を作成し、P2プロトコルのメソッドの実装を記述します。その後、メソッドを実装せずに、P1に対応する型にP2の対応を追加できます。 このアイデアは、POPプレゼンテーション -遡及的採用の例でよく実証されています。
protocol Ordered { func precedes(other: Self) -> Bool } extension Comparable { // , Comparable, : // extension Ordered where Self: Comparable // // extension Comparable where Self: Ordered func precedes(other: Self) -> Bool { return self < other } } extension Int : Ordered {} extension String : Ordered {}
- プロトコルメソッドのデフォルト実装を記述します。 タイプにこのメソッドの実装が含まれる場合、デフォルトの代わりに使用されます。
制約
型の制限。 以下がサポートされています。プロトコルに準拠し、クラスから継承し、タイプを持っています。 制約は、ジェネリック型が持つメソッドのセットを決定するために使用されます。 満足できない型の制約を引数として渡すと、コンパイラはエラーをスローします。
使用場所:
- 一般化された関数の定義におけるパラメーターのタイプの制限。 例:produce関数は、関連付けられたProductタイプがColaでなければならないFactoryプロトコルに準拠するタイプ引数を取ります。
func produce<F: Factory>(factory: F) where F.Product == Cola
別の例:引数は2つのプロトコルに同時に対応する必要があります:動物と飛行:
// : func fly<T>(f: T) where T: Flying, T: Animal { ... } func fly<T: Flying & Animal>(f: T) { ... } func fly<T: Animal>(f: T) where T: Flying { ... } func fly<T>(f: T) where T: Flying & Animal { ... }
- プロトコル定義の関連タイプの制限。 例-連想型識別子はCodableプロトコルに準拠する必要があります。
protocol Order { associatedtype Identifier: Codable }
relatedtype relatedtypeを制約できます。
protocol GenericProtocol { associatedtype Value: RawRepresentable where Value.RawValue == Int func getValue() -> Value } // . : protocol GenericProtocol where Value.RawValue == Int { associatedtype Value: RawRepresentable func getValue() -> Value } protocol GenericProtocol where Value: RawRepresentable, Value.RawValue == Int { associatedtype Value func getValue() -> Value }
- 拡張メソッドの可用性に関する制限。 AnimalおよびFactoryの関数の例を、拡張メソッドに書き換えます。
extension Animal where Self: Flying { func fly() { ... } } extension Factory where Product == Cola { func produce() { ... } }
- 条件付き適合 例:配列の要素の型がObjectWithMassプロトコルに対応する場合、配列自体はこのプロトコルに対応し、質量として、要素の質量の合計を返します。
protocol ObjectWithMass { var mass: Double { get } } extension Array: ObjectWithMass where Element: ObjectWithMass { var mass: Double { return map { $0.mass }.reduce(0, +) } }
関連タイプの定数は、プロトコル自体と、プロトコルが渡されるメソッド、およびプロトコル拡張の両方で指定できるため、定数をどこに追加するかという疑問が生じます。 いくつかの推奨事項:
- プロトコルがアプリケーション固有であり、1つの実装を持つ場合は、関連するタイプではなく特定のタイプを使用する可能性を検討する価値があります。
- プロトコルがアプリケーション固有であり、いくつかの実装がある場合(テスト用の偽物を考慮に入れて)-このプロトコルが使用される場所で複製しないように、プロトコル自体にそれらを配置する方が便利です。
- プロトコルを再利用する計画がある場合、プロトコルにはこれらの定数のみを含める必要があります。これらの定数がないと、プロトコルの存在が意味を成さず、その上にメインロジックが構築されます。 他のすべての定数は、特殊なケースの説明と見なされ、メソッドおよび拡張機能に配置される必要があります。
原則
最高のPOP教材は、デイブ・アブラハムズが主催する
「プロトコル指向プログラミングのスウィフト」セッションです。 視聴することを強くお勧めします。 ほとんどの原則は、そこからの例のおかげで形成されています。
- 「クラスから始めないでください。 プロトコルで開始します。 "。 これは、上記のセッションからのデイブアブラハムスによる声明です。 解釈する方法は2つあります。
- 実装ではなく、契約の説明(オブジェクトが消費者に提供する必要がある機能の説明)から始めます
- クラスではなく、プロトコルで再利用可能なロジックを記述します。 プロトコルをコードの再利用の単位として使用し、クラスを一意のロジックの場所として使用します。 別の方法で、この原則を説明することができます- 変化するものをカプセル化します 。
「テンプレートメソッド」パターンは、優れた類似物です。 彼のアイデアは、一般的なアルゴリズムを実装の詳細から分離することです。 基本クラスには共通のアルゴリズムが含まれ、子クラスはアルゴリズムの特定のステップを再定義します。 POPでは、一般的なアルゴリズムはプロトコル拡張に含まれ、プロトコルは使用されるアルゴリズムのステップとタイプ、およびクラス内のステップの実装を決定します。
- 拡張機能による構成。 多くの人が「継承よりも合成を好む」というフレーズを聞いています。 OOPでは、オブジェクトに異なる機能セット(多態的な動作)が必要な場合、この機能をパーツに分割し、クラスの階層を編成できます。各クラスは祖先から機能を継承して独自に追加するか、バインダーで使用されるインスタンスの階層に関係のないクラスに分割します教室。 拡張機能を使用してプロトコルに適合性を追加する機能を使用すると、補助クラスを作成せずに構成を使用できます。 これは、さまざまなデリゲートに一致するviewControllerを追加するときによく使用されます。 クラス自体にプロトコル適合性を追加することの利点は、より適切に編成されたコードです。
extension MyTableViewController: UITableViewDelegate {
- 継承の代わりにプロトコルを使用します。 Dave Abrahamsはプロトコルをスーパークラスと比較しました。これは、プロトコルが複数の継承を許可するためです。
クラスに多くのロジックが含まれている場合は、ログに記録できる機能の別のセットに分類する価値があります。
もちろん、Cocoaのようなサードパーティのフレームワークを使用している場合、継承は避けられません。
- 遡及モデリングを使用します。
同じセッション「Swiftのプロトコル指向プログラミング」の興味深い例です。 CoreGraphicsを使用してレンダリングするためのRendererプロトコルを実装するクラスを作成する代わりに、このプロトコルへの準拠が拡張機能を介してCGContextクラスに追加されます。 プロトコルを実装する新しいクラスを追加する前に、プロトコル準拠に適応できるタイプ(クラス/構造/列挙型)があるかどうかを検討する必要がありますか?
- プロトコルでオーバーライドできるメソッドを含めます( 要件によりカスタマイズポイントが作成されます )。
特定のクラスのプロトコル拡張で定義された一般的なメソッドを再定義する必要がある場合は、このメソッドの署名をプロトコル要件に転送してください。 他のクラスは編集する必要はありません。 拡張機能のメソッドを引き続き使用します。 違いは言葉遣いにあります-現在は「拡張メソッド」ではなく「デフォルトの実装メソッド」です 。
POPとOOPの違い
抽象化
OOPでは、クラスは抽象データ型の役割を果たします。 POPでは、プロトコル。
Appleによると、抽象化としてのプロトコルの利点(スライド:
「より良い抽象化メカニズム」 ):
- 値型(およびクラス)をサポート
- 静的型関係(および動的ディスパッチ)をサポート
- 非モノリシック
- 遡及モデリングをサポート
- モデルにインスタンスデータを課しません
- モデルに初期化の負担をかけません
- 実装するものを明確にします
翻訳- 値型(およびクラス)のサポート
- 静的な型関係(および動的なディスパッチ)のサポート
- キャストなし
- 遡及モデリングのサポート
- オブジェクトデータを課しません(基本クラスフィールド)
- 初期化に負担をかけないでください(基本クラス)
- 実装するものが明確になります
カプセル化
-システム内のプロパティで、クラス内でそれらと連携するデータとメソッドを組み合わせることができます。
プロトコルにはデータ自体を含めることはできません。このデータが提供するプロパティの要件のみを含めることができます。 OOPの場合と同様に、必要なデータはクラス/構造に含める必要がありますが、関数はクラスと拡張の両方で定義できます。
多型
POP / swiftは、2種類のポリモーフィズムをサポートしています。
- サブタイプ多型。 OOPでも使用されます。
func process(service: ServiceType) { ... }
- パラメトリック多型。 一般的なプログラミングで使用されます。
func process<Service: ServiceType>(service: Service) { ... }
受け入れられるタイプとその関連タイプの機能セットは、制限によって決定されます。 制限を加えることはできませんが、この場合、パラメーターはAnyタイプに似ています。
func foo<T>(value: T) { ... }
サブタイプポリモーフィズムの場合、関数に渡される特定のタイプはわかりません。このタイプのメソッドの実装は、実行時に検出されます(
動的ディスパッチ )。 パラメトリック多相性を使用する場合-パラメータの型はそれぞれコンパイル時に既知であり、そのメソッド(
Static dispatch )。 使用される型はアセンブリ段階で既知であるという事実により、コンパイラは、主にインライン関数を使用することで、コードをより最適化できます。
継承
OOP継承は、親クラスから機能を借用するために使用されます。
POPでは、拡張機能を介して機能を提供するプロトコルに対応を追加することにより、必要な機能が取得されます。 同時に、クラスに限定されず、プロトコルにより構造を拡張し列挙する機能があります。
プロトコルは他のプロトコルから継承できます。これは、親プロトコルの要件を独自の要件に追加することを意味します。
実際にPOPを使用する方法を見てみましょう。
例1
最初の例は、
WWDC 2015-Session 411 Swift in Practiceで発表されたSegueHandlerのアップグレード版です。
RootViewControllerがあり、DetailViewControllerおよびAboutViewControllerの遷移処理を行う必要があるとします。
prepareの典型的な実装
(for:sender :) :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segue.identifier { case "DetailViewController": guard let vc = segue.destination as? DetailViewController else { fatalError("Invalid destination view controller type.") }
idがDetailViewControllerとAboutViewControllerで同じ名前のコントローラークラスを持つ2つの遷移しか持てないことがわかっていますが、不明なseque.identifierチェックとsegue.destinationの型変換を行う必要があります。
このメソッドのコードを改善してみましょう。 可能な遷移の説明から始めましょう-enumはこれに最適です:
enum SegueDestination { case detail(DetailViewController) case about(AboutViewController) }
(注:SegueDestinationはRootViewController内で宣言されます)
私たちの目標は、遷移を処理するための汎用ヘルパーメソッドを記述することです。 これを行うには、遷移を記述する関連するタイプでSegueHandlerTypeプロトコルを定義します。 関連付けられた型の要件-セグエIDとコントローラー型の無効な組み合わせの場合にnilを返す失敗可能な初期化子を提供する必要があります。
protocol SegueHandlerType { associatedtype SegueDestination: SegueDestinationType } protocol SegueDestinationType { init?(segueId: String, controller: UIViewController) }
プロトコルが定義され、移行インスタンスを返すsegueDestination(forSegue :)メソッドを追加します。
extension SegueHandlerType { func segueDestination(forSegue segue: UIStoryboardSegue) -> SegueDestination { guard let id = segue.identifier else { fatalError("segue id should not be nil") } guard let destination = SegueDestination(segueId: id, controller: segue.destination) else { fatalError("Wrong segue Id or destination controller type") } return destination } }
RootViewControllerにSegueHandlerTypeを実装させましょう(この些細なコードが目を引く可能性が低いように、別のファイルに入れてください):
// file RootViewController+SegueHandler.swift extension RootViewController.SegueDestination: SegueDestinationType { init?(segueId: String, controller: UIViewController) { switch (segueId, controller) { case ("DetailViewController", let vc as DetailViewController): self = .detail(vc) case ("AboutViewController", let vc as AboutViewController): self = .about(vc) default: return nil } } } extension RootViewController: SegueHandlerType { }
SegueHandlerTypeのrelatedtypeとRootViewControllerのenumは同じ名前であるため、RootViewControllerのSegueHandlerTypeの実装は空であることに注意してください。 異なる名前があり、列挙がRootViewController内で定義されていない場合、typealiasを使用してプロトコルに関連付けられたタイプを指定する必要があります。
extension RootViewController: SegueHandlerType { typealias SegueDestination = RootControllerSegueDestination }
例の最後の部分-これで準備をリファクタリングできます(for:sender :):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segueDestination(forSegue: segue) { case .detail(let vc):
コードはずっときれいですよね?
もちろん、コードの結果として、さらに多くがありましたが、メインロジック( "// configure vc"コメントの後ろに隠されているロジック)と補助コードを分離することができました。 長所-コードが読みやすくなり、補助SegueHandlerTypeを再利用できます。
例2
UITableViewで要素のリストを表示するための典型的なタスクを検討してください。
初期データとして、
Catモデルと、
CatRepositoryプロトコルに対応する
TestCatRepositoryがあります。
struct Cat { var name: String var photo: UIImage? } protocol CatRepository { func getCats() -> [Cat] }
テーブルとセルコントローラークラスがプロジェクトに追加されます:
CatListTableViewController 、
CatTableViewCell 。
一般化リストプロトコルを説明してみましょう。 他のテーブルをプロジェクトに追加する計画があると想像してください。これには、いくつかのセクションが含まれます。 プロトコル要件:
- セクション内の要素の数を設定できるはずです。
- セクションインデックスと要素インデックスのタイプを指定できるようにする必要があります-それらは任意です-数値、列挙、タプル、それらに要件を課しません;
- セルタイプ-要件を課すこともせず、TableViewCellにすることもできます。テーブルで異なるタイプのセルが使用される場合、特定のタイプのセルを取得するためにより複雑なタイプにすることができます(異なるセル識別子)
- セルを更新する要求を処理する機能-最も簡単な方法は、2つのパラメーター(セルとそのインデックス)を持つ関数の形式でハンドラーを割り当てることです。
作成された要件を考慮して、プロトコルを記述します。
protocol ListViewType: class { associatedtype CellView associatedtype SectionIndex associatedtype ItemIndex func refresh(section: SectionIndex, count: Int) var updateItemCallback: (CellView, ItemIndex) -> () { get set } }
猫に関する情報を表示するためのセルの要件を説明しましょう。
protocol CatCellType { func setName(_: String) func setImage(_: UIImage?) }
このプロトコルへの通信をCatTableViewCellクラスに追加します。
メインプロトコルであるListViewTypeをCatListTableViewControllerに追加する必要があります。 CatTableViewCellの1つのタイプのみを使用しているため、関連付けられたタイプのCellViewとして使用します。 テーブルにはセクションが1つしかなく、要素の数は事前にわかりません。SectionIndexとItemIndexとして、それぞれVoidとIntを使用します。
CatListTableViewControllerの完全な実装:
class CatListTableViewController: UITableViewController, ListViewType { var itemsCount = 0 var updateItemCallback: (CatTableViewCell, Int) -> () = { _, _ in } func refresh(section: Void, count: Int) { itemsCount = count tableView.reloadData() } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return itemsCount } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CatCell", for: indexPath) as! CatTableViewCell updateItemCallback(cell, indexPath.row) return cell } }
現在の目標は、CatRepositoryとListViewTypeをリンクすることです。 ただし、アルゴリズムを特定のCatモデルに関連付けたくありません。 これを行うために、モデルタイプがrelatedtypeでレンダリングされる一般化されたプロトコルを区別します。
protocol RepositoryType { associatedtype Model func getItems() -> [Model] } protocol ConfigurableViewType { associatedtype Model func configure(using model: Model) }
新しいプロトコルへのコンプライアンスを追加します。
extension CatRepository { func getItems() -> [Cat] { return getCats() } } extension TestCatRepository: RepositoryType { } extension CatCellType where Self: ConfigurableViewType { func configure(using model: Cat) { setName(model.name) setImage(model.photo) } } extension CatTableViewCell: ConfigurableViewType { }
ListViewTypeリストのRepositoryTypeによって提供されるオブジェクトを表示するメソッドを実装する準備がすべて整いました。 アルゴリズムは複数のセクションをサポートしませんが、インデックスとしてIntを使用します。 拡張機能に制限を追加します。
extension ListViewType where SectionIndex == (), ItemIndex == Int { ... }
CatListTableViewControllerはこれらの制限に準拠しています。
しかし、これらはすべての制限ではありません-ListViewType.CellViewはConfigurableViewTypeでなければならず、そのモデルタイプはRepositoryType.Modelでなければなりません:
func setup<Repository: RepositoryType>(repository: Repository) where CellView: ConfigurableViewType, CellView.Model == Repository.Model { ... }
そして、クラスはこれらの制限を満たしています。
完全な拡張コード:
extension ListViewType where SectionIndex == (), ItemIndex == Int { func setup<Repository: RepositoryType>(repository: Repository) where CellView: ConfigurableViewType, CellView.Model == Repository.Model { let items = repository.getItems() refresh(section: (), count: items.count) updateItemCallback = { cell, index in let item = items[index] cell.configure(using: item) } } }
メインロジックの準備ができました。AppDelegateでこの関数を使用します。
let catListTableView = window!.rootViewController as! CatListTableViewController let repository = TestCatRepository() catListTableView.setup(repository: repository)
完全なサンプルコードは
こちらにあります 。
この例では、ロジックは、全体として機能する多くの小さな独立した部分に分割されています。ロジックのほとんどはクラスではなく拡張機能にあるため、一見どのクラスがどの責任を負うのかは明確ではありません。したがって、疑問が生じます。この例はどのアーキテクチャに起因するのでしょうか?使用される機能は、ListViewType拡張機能にあります。このロジックは、このプロトコルに準拠しているため、CatListTableViewControllerクラスで使用できます。CatListTableViewControllerのコンシューマーは、これをその機能と見なします。 catListTableView.setup(repository: repository)
したがって、CatListTableViewController-MVCのコントローラーの役割。したがって、アプリケーションアーキテクチャはMVCですが、コードの構成は変わっています。おわりに
プロトコル指向プログラミングは、汎用プログラミングと特性の概念に依存しています。POPを使用すると、コードの再利用性が向上し、構造化されたコードが改善され、コードの重複が減少し、クラス継承階層の複雑さが回避され、コードの接続性が向上します。ソース:
- WWDC 2015「Swiftのプロトコル指向プログラミング」
- WWDC 2015「Swift in Practice」
- WWDC 2016「UIKitアプリのプロトコルと価値指向プログラミング」
- タイプ消去
- 特性:構成可能な行動単位