スイフトの代表パターン



イントロ
代表団は、アップルのフレームワークで一般的に使用されるパターンですが、私は多くの人々は、彼らは最初のIOSの開発を学んでいるときにそれの周りに頭を包む苦労していることがわかります.そして、それを定義するのに用いられる用語が特に「初心者親しみやすい」でないので、それは不思議でありません.
この引用をSwift Language Guide :

This design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated.


またはJohn Sundell's article on the topic :

The core purpose of the delegate pattern is to allow an object to communicate back to its owner in a decoupled way. By not requiring an object to know the concrete type of its owner, we can write code that is much easier to reuse and maintain.


あなたがしばらくプログラミングしているならば、それらの文はおそらくあなたに何かを意味します.しかし、あなたがちょうど出発しているならば、あなたがちょうど若干の認知的な過負荷を経験して、何が何の委任であるかについてより何かを知っていることから本当に離れて来ない良いチャンスがあります.では、どのような代表団があるのか、なぜ私たちがそれを使用しているかを見てみましょう.

代表団は何ですか.
コンセプト自体はかなり簡単です.私は比喩でそれを説明することができます.エグゼクティブが彼らがしたいと思っているいくつかの仕事と彼らが答えたいと思ういくつかの仕事があると想像してください.たぶん、彼らは時間を持っていない、または彼らはスキルを持っていない、または何でも、それは本当に重要ではない.この人は私たちの「代表者」です.それで、代表団がする仕事は、「私はこの1つの仕事をする方法を知っていて、これらの2つの質問に答える方法を知っている誰かを必要とする」と言いますその人は「代表」です.彼らは彼らがどのようにタスクを行うには、質問の答えを知っていることを確認して契約書に署名する限り、代表は、世界の誰でもすることができます.その後、任意の時点では、委任は、そのタスクを行う必要がありますまたは質問は、彼らは実際に仕事をして、結果を戻す代表団にそれをオフに渡します.
Swiftでは、「ジョブ記述」をプロトコルと呼びます.他の言語ではインターフェイスと呼ばれます.によるとSwift Language Guide : 「プロトコルは、特定の仕事または機能の部分に合う方法、特性と他の要件の青写真を定義します.」私に仕事の説明のように聞こえる.そして、クラスがプロトコルを採用するとき、それは契約に署名しています.それは私とあなたと、コードの残りの部分、およびコンパイラが、そのプロトコルで説明されたすべてを行うことができると言います.
したがって、例のシナリオをコードとして書くと、次のようになります.
// This protocol will hold everything the manager class needs their assistant to do
protocol ManagerDelegate {
    func doLaundry(for manager: Manager, laundry: [Clothes]) -> [Clothes]
    func howMuchMoneyDidWeMake() -> Int
    func whatBearIsBest() -> String
}

class Manager {
    // the manager doesn't care what class their assistant is,
    // only that they can do what the manager needs them to do
    weak var assistant: ManagerDelegate? 

    // whenever the manager needs those tasks done, they just have the assistant do it
    func performEndOfDayTasks() {
        if today.isMonday {
            cleanClothes = assistant.doLaundry(for: self, laundry: dirtyClothes)
        } else if today.isFriday {
            let profit = assistant.howMuchMoneyDidWeMake()
            tellBoss("We made \(profit) dollars this week boss!")
        }
    }
}

// The assistant adopts the protocol and this code will not compile
// unless they meet all the requirements of the protocol
class Assistant: ManagerDelegate {
    func doLaundry(for manager: Manager, laundry: [Clothes]) -> [Clothes] {
        var cleanClothes = washClothes(laundry) 
        dryClothes(cleanClothes)
        foldClothes(cleanClothes)
        return cleanClothes
    }
    func howMuchMoneyDidWeMake() -> Int {
        return ledger.profit
    }
    func whatBearIsBest() -> String {
        return "There are basically two schools of thought..."
    }
}

なぜ代表団を使用しますか?
それで、それは委任が何であるか、そして、それがどのように見えるかもしれないかについて説明します、しかし、誰でもなぜ最初にそれをしたいかについて説明しません.管理者はなぜ自分で仕事をすることができませんか?我々が以前に言ったように、現実の世界では、彼らは時間によって制約されるかもしれません(一日で十分な時間でない)か、能力(スキルを持っていない)によって制約されるかもしれません、あるいは、彼らは願望によって制約されるかもしれません.我々がコードについて話しているとき、我々のコードが(まだ)感情的でないので、比喩は少しここで故障します、そして、それは本当に少しの欲求も持ちません.我々が我々のコードで代表団を使用する理由のカップルは、ここにあります:
それは懸念の適切な分離を促進する.我々Manager クラスは、管理のために必要なすべてのロジックを保持することができますし、支援のロジックを乱雑にする必要はありません.それはassistant そのために.これは私たちのコードを読みやすくし、理由を簡単になります.管理ロジックを更新する必要がある場合、または支援のバグを追跡しようとしている場合は、右の場所を知っている.
それは私たちのコードを切り離します.つまり、それぞれのクラスは直接他のクラスに結び付けられていない.それぞれのクラスのロジックを個別にテストすることができます.そして、我々が新しいものを書きたいならばExecutiveAssistant クラスは、我々はそれを行うことができますし、どのように影響を与えることなく、それを交換する方法Manager 適切なプロトコルを採用している限り動作します.我々がチームで働くならば、1人の人はManager クラスと他の人はAssistant 対立のないクラス.これは、人々が2つのクラスが「緩く結合している」と言うとき、意味するものです.我々のものならば、ものはより「きつく結合されます」Manager アシスタントを定義var assistant: Assistant . 我々の現実世界の比喩では、緩やかな結合は“私はこの仕事の説明に合うアシスタントを必要とする”とタイトなカップリングは“私は私のアシスタントとしてドワイトを必要とするようなものではない、誰もしない”というものです.
再利用を促進現実の世界では(理論的に)1つのポジションからマネージャーを引き抜き、別の場所に配置することができます.そして、彼らが一般的に人々を管理する方法を知っている限り、彼らがこれらの人々またはそれらの人々を管理しているならば、それは重要ではありません.彼らは繁栄し続けるべきだ.この人やその人を援助しているかどうかはアシスタントにも言える.このコードには同様の例があります.テーブルビューについて考えてください.画面上に垂直スクロールテーブルをレンダリングするために使用するロジックがたくさんあります.あなたはすべてのセルを管理する必要がある場合は、あなたのアプリケーションは、長いリストのためのビューの数千を生成しないように再利用されていることを確認している場合、それはおそらく良いでしょう.そこにロジックがたくさんあるので、私たちは私たちのアプリでテーブルを必要とするたびに書く必要はありませんので、我々はそれを再利用することができるクラスがあれば素晴らしいだろう.ヒッチは、各テーブルに表示するコンテンツが異なることです.それが起こると、アップルは私たちの解決策を思いつきましたUITableView (現代の読者は好むがよいがUICollectionView ), そして、我々はそれが再利用されることができるそれ自身のクラスで包まれるテーブルをレンダリングするために論理を保つ間、我々が我々の個人のアプリのために論理を定めるのを許す代表団を使用しました.
すべての理由があなたのために十分でないならば、あなたは委任を使用せずにあなた自身のコードの全てを書くことができたと思います.ターゲットアクションやコールバックのような他のパターンを使用することができます同じもの(多くの他のトレードオフ)の多くを達成する.しかし、IOSのアプリを開発している場合は、いくつかのポイントでいくつかの代表を実装する必要がありますので、それはアップルのフレームワークの一般的なパターンですので、パターンを快適に得ることがあります.

代表団の使い方
私は数年間のIOSのアプリを開発しているので、私は私の公正なシェアを実装しているUITableViewDelegateUICollectionViewDataSource しかし、私はまた、私自身の意見で代表パターンを使用するのが好きです.私は自分のUIをAにレイアウトする傾向があるUIView サブクラス、そのビューのデリゲートを定義し、デリゲートに発生するイベントを渡します.だから、ビューのボタンがある場合は、最も頻繁にその所有者に渡されるそのボタンをタップのユーザーの行動UIViewController ). 通常このように見えます.
まず、デリゲートプロトコルを定義します.これは、我々のビューがデリゲートに中継する必要があるすべてのメッセージになります.この例では、ボタンがタップされていることを知らせるだけですが、プロダクションコードでは、この関数のより具体的で通信可能な名前が欲しいでしょう.
protocol SomeDetailViewDelegate: AnyObject {
    func didTapButton()
}
その後、ビュー自体.それはデリゲートですweak . 理由は、この記事の範囲を超えていますが、単純な答えは確かに確認することによって保持サイクルを防ぐために役立つてSomeDetailView それが代表者を保持しません.あなたが望むならread more about how that works here . また、我々は我々のプロトコルがマークされていることに注意してくださいAnyObject . それはコンパイラが私たちにそれをマークさせないからですweak それが参照型であることが知られていない、そして、それがまさに何であるかについて、見解でAnyObject です.
class SomeDetailView: UIView {

    weak var delegate: SomeDetailViewDelegate?
    private let button = UIButton()

    // somewhere in the configuration and layout of button and other views
    button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)

    @objc private func didTapButton(_ sender: UIButton) {
        delegate?.didTapButton()
    }
}
ビュー自体はかなりダムであることがわかります(ビジネスロジックがあれば、あまり保持しないという意味です).それはちょうどUIがどのように見えるかを定義し、その後、それがデリゲートに沿って取得するメッセージを渡します.
次に、このビューを保持するビューコントローラを持っています.これは、ビューへの強力なリファレンスを持っており、画面(ここでは示されていない)にそれを置くための責任があります.若干のポイントで、なるべくならビューがその代表団にいかなるメッセージも送ることが可能である前に、ビューコントローラはそれ自体をビューの代表としてセットする.
class SomeDetailViewController: UIViewController {
    private lazy var detailView: SomeDetailView = {
        let detailView = SomeDetailView()
        detailView.delegate = self
        return detailView
    }()
}
今、それをコンパイルするために、我々のViewコントローラはデリゲートプロトコルを採用しなければなりません.ここでは、拡張でそれをしています.これは必須ではありませんが、かなり一般的なパターンであり、そのプロトコルに関連するすべてのロジックを1つの場所でラップするのに最適な方法です.必要に応じて別のファイルに置くこともできます.どのようにプロトコルに準拠するかに関係なく、これはユーザがそのボタンをタップしたときの操作のロジックです.
extension SomeDetailViewController: SomeDetailViewDelegate {
    func didTapButton() {
        print("Do something when the user taps the button")
    }
}
この組織は、以前議論されたすべての利点につながります.これはビジネスロジックからUIロジックを分離するので、レイアウトのバグがある場合はどこを見て知っている.それは私たちのビューのコントローラからビューを変更するには、ビジネスロジックとその逆の効果を持つビューに変更を行うことができます.そして再利用を促進する.任意のビューコントローラ(または別のビュー)は、このビューをプラグインすることができますし、それが行う必要があるすべての方法は、1つの新しいプロトコルメソッドに準拠する方法を決定することです.そして、それが一貫してコードベースを通してされるならば、他の誰でも我々のコードに飛び込むことができて、どこで説明のわずか数分で見てもよいかについてわかっています.

包む
とても簡単に要約します.
  • 委任は、ちょうどその仕事をすることができる誰か/他の何かに若干の仕事を配っています.
  • あなたのクラスを分離するために、そして、あなたのコードをより再利用できるように、懸念を分離するために代表団を使ってください
  • 委任を開始する良い場所は、ビューとビューコントローラの間です
  • 私は、これは、特にIOSの開発で始まっている場合に役立ちます願っています.そしてもしそれが、私に知らせてください!多分、あなたは代表的なパターンが退屈であると思うより経験豊かな開発者であるか、時代遅れで、我々はもうそれを使わないでください.私にあなたのすべての考えと意見も知らせさせてください.
    これが役に立つならばbuy me a coffee!