Sponsor Link
enumのすすめ(struct/class との使い分け)
環境&対象
- macOS Monterey 12.4
- Xcode 13.3.1
- iOS 15.4
enum のすすめ
Swift でデータ構造を表現するときに、struct や class を最初に思い浮かべる人は多そうです。
struct と class の使い分けについては以下で説明しています。
[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” かもしれません。代入する側が 気をつけて代入する必要がありますし、使う側も チェックして使う必要があるかもしれません。
もちろん、Character の持てる範囲 と 表したい範囲が一致しているときは、Character を使うのが自然です。
このように、表現したい範囲が限定されていて かつ その範囲内であることを確実に保証することは、enum が得意とすることです。
Character の持てる1文字とは、Unicode での1文字なので、🇯🇵 などのように コード的には2つの値の組み合わせで表現される文字でも 持つことができて面白いです。
Apple のドキュメントは、こちら。
まとめ
enum の概要と struct/class との使い分けを説明しました。
- enum は、有限個の状態のいずれかであることを表現する
- enum は、Value-type
- class との使い分けは、struct と同様に 同一性の扱い方による
- struct との使い分けは 有限個の状態を表現したいかどうか
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link