web-dev-qa-db-ja.com

Swiftでクラス拡張を適切に使用する方法

Swiftでは、これまで拡張機能を使用してクローズド型を拡張し、アニメーションや数学拡張などの便利なロジックレスの機能を提供してきました。ただし、拡張機能はコードベース全体に散在するハードな依存関係であるため、実装する前に常に3回考えます拡張としての何か。

しかし最近、Appleは、拡張機能をさらに拡張して使用することを提案しています。たとえば、プロトコルを個別の拡張機能として実装します。

つまり、プロトコルBを実装するクラスAがある場合、次の設計になります。

class A {
    // Initializers, stored properties etc.
}

extension A: B {
    // Protocol implementation
}

そのうさぎの穴に入ると、次のような拡張機能ベースのコードが表示されるようになりました。

fileprivate extension A {
    // Private, calculated properties
}

fileprivate extension A {
    // Private functions
}

私の一部は、個別の拡張機能でプロトコルを実装したときに得られるビルディングブロックが気に入っています。これにより、クラスの個別の部分が明確になります。ただし、このクラスを継承するとすぐに、拡張機能をオーバーライドできないため、この設計を変更する必要があります。

2番目のアプローチは...興味深いと思います。拡張機能にそれを指定できるので、各プライベートプロパティに注釈を付けたり、プライベートとして機能したりする必要がないという点で優れています。

ただし、この設計では、格納されているプロパティと格納されていないプロパティ、パブリック関数とプライベート関数も分割されているため、クラスの「ロジック」をたどることが難しくなっています(小さいクラスを記述します)。それは、サブクラス化の問題とともに、拡張機能の不思議の国のポーチで少し停止させます。

Swift世界中のコミュニティが拡張機能をどのように見ているかを聞いてみたいと思います。どう思いますか?Silverbulletはありますか?

17
Daniel Saidi

もちろん、これは私の意見に過ぎないので、簡単に書いてください。

現在、いくつかの理由により、プロジェクトでextension-approachを使用しています。

  • コードははるかにクリーンです:クラスが150行を超えることはなく、拡張機能による分離により、コードが読みやすくなり、責任によって分離されます

これは通常、クラスは次のようになります。

final class A {
    // Here the public and private stored properties
}

extension A {
    // Here the public methods and public non-stored properties
}

fileprivate extension A {
    // here my private methods
}

もちろん、拡張機能は複数になる可能性があります。これは、クラスの機能によって異なります。これは、コードを整理してXcodeのトップバーから読み取るのに便利です。

extension description

  • Swiftプロトコル指向プログラミング言語であることを思い出します、 OOP言語ではありません。プロトコルとプロトコル拡張機能ではできないことはありません。また、クラス/構造体にセキュリティ層を追加するためにプロトコルを使用することを好みます。たとえば、私は通常、このように私のモデル:

    protocol User {
        var uid: String { get }
        var name: String { get }
    }
    
    final class UserModel: User {
        var uid: String
        var name: String
    
        init(uid: String, name: String) {
            self.uid = uid
            self.name = name
        }
    }
    

このようにして、uidクラス内でnameおよびUserModelの値を編集できますが、Userプロトコルタイプのみを処理するため、外部では編集できません。

10
Luca D'Alberti

私は1つの文で説明できる同様のアプローチを使用しています。

タイプの責任を拡張機能に分類する

これらは、私が個々の拡張機能に入れている側面の例です。

  • クライアントから見たタイプのメインインターフェース。
  • プロトコルへの準拠(つまり、デリゲートプロトコル、多くの場合プライベート)。
  • シリアライゼーション(たとえば、NSCoding関連すべて)。
  • ネットワークコールバックなど、バックグラウンドスレッドに存在する型の一部。

場合によっては、1つの側面の複雑さが増すと、型の実装を複数のファイルに分割することさえあります。

ここでは、実装関連のコードを並べ替える方法を説明する詳細をいくつか示します。

  • 焦点は機能メンバーシップです。
  • パブリック実装とプライベート実装を近づけますが、分離してください。
  • varfuncを分割しないでください。
  • ネストされた型、初期化子、プロトコル準拠など、機能の実装のすべての側面をまとめてください。

利点

タイプの側面を分離する主な理由は、読みやすく理解しやすくするためです。

外国の(または私自身の古い)コードを読むとき、全体像を理解することは、多くの場合、飛び込むのが最も難しい部分です。開発者にいくつかのメソッドのコンテキストのアイデアを与えることは、多くの助けになります。

別の利点もあります。アクセス制御により、誤って何かを呼び出さないようにすることが容易になります。バックグラウンドスレッドからのみ呼び出されることになっているメソッドは、「バックグラウンド」拡張機能でprivateと宣言できます。今では他の場所から呼び出すことはできません。

現在の制限

Swift 3はこのスタイルに一定の制限を課しています。メインタイプの実装でしか実行できないことがいくつかあります。

  • 保存されたプロパティ
  • func/varのオーバーライド
  • オーバーライド可能なfunc/var
  • 必要な(指定された)初期化子

これらの制限(少なくとも最初の3つ)は、オブジェクトのデータレイアウト(および純粋なSwiftのウィットネステーブル)を事前に知っておく必要があるためです。拡張機能は実行時に遅く読み込まれる可能性があり(フレームワーク、プラグイン、dlopenなどを介して)、インスタンスが作成された後に型のレイアウトを変更すると、ABIが機能しなくなります。

Swift team :)のささやかな提案

1つのモジュールのすべてのコードが同時に使用できることが保証されています。 Swiftコンパイラーがタイプを「構成」することを許可する場合、機能的な側面を完全に分離することを妨げる制限単一モジュール内。構成タイプでは、コンパイラーは、モジュール内のすべてのファイルから型のレイアウトを定義するすべての宣言を収集します言語の他の側面と同様に、ファイル内の依存関係を自動的に見つけます。

これにより、実際に「アスペクト指向」の拡張機能を作成できます。格納されたプロパティまたはオーバーライドをメインの宣言で宣言する必要がないと、アクセス制御が向上し、問題を分離できます。

5
Nikolai Ruhe

私はそれが嫌いです。これにより、複雑さが増し、拡張機能の使用が混乱し、人々が拡張機能をどのように使用するのかが明確になりません。

プロトコルに準拠するために拡張機能を使用している場合は、わかりましたが、なぜコードにコメントしないのですか?これはどうですか?わかりません。

0
Alex Zavatone