[Swift] enum のすすめ

     

TAGS:

enumのすすめ(struct/class との使い分け)

あまり使われていない enum の使い方を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 12.4
  • Xcode 13.3.1
  • iOS 15.4

enum のすすめ

Swift でデータ構造を表現するときに、struct や class を最初に思い浮かべる人は多そうです。

struct と class の使い分けについては以下で説明しています。
Swift[Swift] struct, class どちらを使うべき?

もちろん、struct や class を使うべきケースは多くありますが、同じくらい enum が使用されるべきケースがあります。

Swift の Language Guide にも struct や class の前に、enum が紹介されるくらいです。

以下では、enum がどのようなデータを表現できるかを確認し、struct や class との使い分けを見ていきます。

enum で表現できること

enum は、Swift だけの特別な要素ではなく、C 言語の時代(?)から存在していた要素です。基本は同じです。
# ただし Swift の enum は拡張されているので 使い勝手は良くなっています。

enum が使われるのは、以下のようなケースです。

・有限個の状態のいずれかであることを表現するとき

例えば、以下がその例です。


enum MyState {
   case low
   case mid
   case high
}

var state: MyState = .high

enum である MyState は、low, mid, high のいずれかの状態を "必ず" 持つ ような型として定義されています。
つまり enum で定義された 型 MyState は low, mid, high という情報の集合を表し、MyState 型の変数は、その集合のうちのいずれか1つの値を持つということになります。

上記の変数 state は、high という状態を表しています。

var で定義されていますので、どこかで mid や low に変更されるかもしれませんが、high/mid/low のいずれかの値だけを取ります。そのことは、Swift が保証してくれます。異なる値を代入しようとすると、コンパイルエラーになります。

当たり前に見えるかもしれませんが、このことが、非常に重要な特徴になります。

もう少し見てみます。

Int で表してみる

同様のことを enum を使わずに、Int 型の変数で表現してみます。


var state: Int = 2 // 0: low    1: mid      2: high

値 0 を持つときには、low という状態を表し、値 1 を持つときは mid、値 2を持つときは high いう状態を表すと"決めておく"ことで、上記のように書くことができます。
この方法の懸念点は、「Int 型の変数には、0 / 1 / 2 以外の値を代入することができる」という点です。enum が特定の集合のいずれかの値を持つのとは対照的です。

もちろん、(すぐには) 意図して想定していない値である 3 や 4 を代入しないとは思いますが、時間が経つと どのような値をセットすべきかの記憶が薄れてしまい 思いがけず 想定していない値を入れてしまうこともあるかもしれません。
enum であれば、許されない値の代入はコンパイルエラーになります。ですが 変数の型が Int であれば Swift 言語上代入可能な値であれば、Swift のコンパイラーはチェックすることができません。

それを防ぐには、"常に" プログラムを書いている人間が "気をつける" 必要があり、コンパイラーはチェックしてくれないので、気をつける以上の対策がありません。
凝ったUI や 複雑なアルゴリズムを記述することに使いたい気力を 「この Int 変数には、0 / 1 / 2しか入れてはいけない」ということを覚えておくためにも使う必要があります。

このことが、enum の代わりに Int を使用することの 最大の懸念点です。

enum の特徴は、「有限個の集合を定義し、そのいずれかを値として持つ変数を定義することができる」です。

複雑な機能ではありませんが、enum を適切に使用することで、コードの質を向上させることにつながります。

struct/class との使い分け

struct や class との使い分けを考える前に 覚えておくことは、enum は、Value-type であるということです。
(struct は、Value-type であり、class は、Reference-type です。)

enum と class の使い分け

struct と class の使い分けの基本的なところは、enum と class の使い分けにも適用できます。

その要素の同一性をどう考えるかです。

同じ値がメモリ上の別の場所に存在した時に、それは ”同じ” と言えるのかどうかということです。

”同じ”とは、同値判定に関連するだけではありません。ここでの”同じ”とは、その要素に加えた変更を別の場所でも同じように 変更として影響を受けたいかということも含まれます。

enum は、struct と同じように Value-type であるので、関数に渡したときにコピーが行われます。
つまり 受け取った側で何らかの変更を行っても、渡した側の持っている enum は、変更されません。影響されないことを期待するケースと、影響されたいケース はどちらもケースとして存在し、どちらが正しいということではありません。

enum と structの使い分け

enum と struct はどちらも Value-type です。

同一性を考慮して使い分けることはありません。純粋に その機能で使い分けることになります。

enum と struct の違いは、その 表現できる範囲の違いです。

文字を表す enum と struct を作ってみるとその違いがわかります。

struct MyStruct {
var chrValue: Character
}

enum MyEnum {
case A, B, C
}

MyStruct は、Character で表せる値全てを持つことができます。

ですが、MyEnum の定義をしようとすると、文字としてどのような値を持つのかを決める必要が出てきます。
このことが、enum の特徴です。

例えば、A, B, C のいずれかの値でなければいけない変数を考えます。

MyEnum を使って変数定義すれば、それは "必ず" A, B, C のいずれかの値であることが保証されています。

もちろん、MyStruct でも、"A", "B", "C" の値を持つことはできますが、あくまで Character という型で持っているために、"D" かもしれず、"E" かもしれません。代入する側が 気をつけて代入する必要がありますし、使う側も チェックして使う必要があるかもしれません。

MEMO
もちろん、Character の持てる範囲 と 表したい範囲が一致しているときは、Character を使うのが自然です。

このように、表現したい範囲が限定されていて かつ その範囲内であることを確実に保証することは、enum が得意とすることです。

MEMO
Character の持てる1文字とは、Unicode での1文字なので、🇯🇵 などのように コード的には2つの値の組み合わせで表現される文字でも 持つことができて面白いです。
Apple のドキュメントは、こちら

まとめ

enum の概要と struct/class との使い分けを説明しました。

enum の概要と struct/class との使い分け
  • enum は、有限個の状態のいずれかであることを表現する
  • enum は、Value-type
  • class との使い分けは、struct と同様に 同一性の扱い方による
  • struct との使い分けは 有限個の状態を表現したいかどうか

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。