テックノート

StructとClass間の選択

データの保存方法や動作のモデル化を決める。
代表者 ロジャース リチャード 投稿者: ロジャース リチャード
Swift

パワフルなオープン開発言語を使ってアプリケーションを作ることができます。

概要

struct(構造体)とclassは、アプリ内でデータを保存したり、動作をモデル化したりするのに適した選択肢ですが、その類似性から、どちらかを選択するのが難しい場合があります。

アプリに新しいデータタイプを追加する際には、以下の推奨事項を参考にして、どのオプションを選択するのが妥当かを検討してください。

  • デフォルトではstructを使用します。
  • Objective-Cの相互運用性が必要な場合は、classを使用してください。
  • モデル化するデータのアイデンティティを管理する必要がある場合は、classを使用します。
  • プロトコルとともにstructを使用し、実装を共有することで動作を採用する。

デフォルトで構造を選択

structを使って一般的な種類のデータを表現します。Swiftの構造体は、他の言語ではclassに限定される多くの機能を含んでいます。それらは、保存されたプロパティ、計算されたプロパティ、およびメソッドを含むことができます。さらに、Swiftの構造体は、デフォルトの実装によって動作を得るためにプロトコルを採用することができます。Swiftの標準ライブラリとFoundationでは、数値、文字列、配列、辞書など、頻繁に使用するタイプに構造体を使用します。

structを使用すると、アプリ全体の状態を考慮することなく、コードの一部を簡単に推論することができます。structclassとは異なり値型であるため、アプリのフローの一部として意図的に変更を伝えない限り、structに対するローカルな変更はアプリの他の部分からは見えません。その結果、コードのあるセクションを見て、そのセクションのインスタンスに対する変更が、関連する関数の呼び出しから目に見えない形で行われるのではなく、明示的に行われることに自信を持つことができます。

Objective-Cの相互運用性が必要な場合は、クラスを使用する。

データを処理する必要のある Objective-C API を使用する場合や、Objective-C フレームワークで定義された既存のクラス階層にデータ・モデルを適合させる必要がある場合は、クラスとクラス継承を使用してデータをモデル化する必要があるかもしれません。例えば、多くのObjective-Cフレームワークでは、サブクラス化することが求められるクラスが公開されています。

アイデンティティを管理するためにクラスを使用する

Swiftのクラスは、参照型であるため、アイデンティティのビルトインの概念を持っています。これは、2つの異なるクラスインスタンスがそれらの保存されたプロパティのそれぞれに同じ値を持っているとき、それらはまだアイデンティティ演算子(===)によって異なるものとみなされることを意味します。また、アプリ全体でクラスのインスタンスを共有している場合、そのインスタンスに加えた変更は、そのインスタンスへの参照を保持しているコードのすべての部分で見ることができます。クラスは、インスタンスがこのような識別性を持つ必要がある場合に使用します。一般的な使用例としては、ファイルハンドル、ネットワーク接続、CBCentralManagerのような共有ハードウェアの仲介などがあります。

例えば、ローカルのデータベース接続を表す型がある場合、そのデータベースへのアクセスを管理するコードは、アプリから見たデータベースの状態を完全に制御する必要があります。この場合、クラスを使用することは適切ですが、アプリのどの部分が共有データベースオブジェクトにアクセスできるかを必ず制限してください。

重要: アイデンティティの取り扱いには注意が必要です。アプリ全体でクラスインスタンスを広く共有すると、ロジックエラーが発生しやすくなります。頻繁に共有されているインスタンスを変更した場合の結果は予想できないので、そのようなコードを正しく書くのはより大変です。

アイデンティティをコントロールできない場合のstructの使用

structは、自分ではコントロールできないアイデンティティを持つエンティティに関する情報を含むデータをモデル化する場合に使用します。

例えば、リモートのデータベースを参照するアプリでは、インスタンスのアイデンティティは外部のエンティティが完全に所有し、識別子によって伝達される場合があります。アプリのモデルの一貫性がサーバーに保存されている場合、識別子を持つ構造体としてレコードをモデル化できます。以下の例では、jsonResponse には、サーバーからのエンコードされた PenPalRecord インスタンスが含まれています。

struct PenPalRecord {
    let myID: Int
    var myNickname: String
    var recommendedPenPalID: Int
}

PenPalRecord のようなモデル タイプのローカルな変更は便利です。たとえば、アプリがユーザーのフィードバックに応じて複数の異なるペンパルを推奨する場合があります。PenPalRecord 構造は、基礎となるデータベース レコードの ID を制御しないため、ローカルの PenPalRecord インスタンスに加えられた変更が、データベース内の値を誤って変更するリスクはありません。

アプリの他の部分がmyNicknameを変更してサーバに変更要求を送り返しても、直近に拒否されたペンパルの推薦が誤って変更されて拾われることはありません。myIDプロパティは定数として宣言されているため、ローカルでは変更できません。そのため、データベースへのリクエストで間違ったレコードを変更してしまうことはありません。

構造体とプロトコルを使って、継承と動作の共有をモデル化する

structclassは、どちらも一種の継承をサポートしています。structとプロトコルはプロトコルのみを採用することができ、classを継承することはできません。しかし、classの継承で構築できる継承階層は、プロトコルの継承やstructでもモデル化することができます。

継承関係をゼロから構築する場合は、プロトコル継承をお勧めします。プロトコル継承では、クラス、構造体、列挙体が継承に参加できますが、クラス継承では、他のクラスとの互換性しかありません。データをどのようにモデル化するかを決める際には、まずプロトコル継承でデータ型の階層を構築し、次にstructにプロトコルを採用してみてください。