1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Swift】共用型の列挙型

Posted at

###1.はじめに

前回に列挙型について解説しましたが、今回は事前にお伝えした通り、共用型の列挙型について解説をしていこうと思います。

前回の記事と非常に深い関わりがあるので、まだご覧になっていない方はこちらの記事もご覧ください。
(https://qiita.com/0901_yasyun/items/a87c49438db68241778b)

###2.共用型の列挙型の概要について

共用型の列挙型は、実体型を指定しないシンプルな列挙型と、複数の異なるタプルの構造を併せ持つことができる型です。
この型にはいく通りかの異なるデータを、共通の概念でまとめて扱えるようにする狙いがあります。

まず、共用型の列挙型の定義の概要を次に示します。
シンプルな列挙型のケース名後ろにタプルの宣言を付けた形になっています。

enum 型名 {
    case ケース名 タプル型宣言
    case ケース名 タプル型宣言
    ... ...

次に具体的な定義例を示します。
この例では、Webページなどの作成などで使用する色の指定は、文字列の色名、16進数によるカラーコードなどで行われますが、どの形式でも色を指定するという目的は同じなので、これを1つのデータ型で表すことを考えます。

enum WebColor {
    case name(String)       // 色の名前
    case code(String)       // 16進カラーコード
    case white, black, red  // よく使う色
}

この定義を使って、様々なインスタンスを生成できます。

let background = WebColor.name("indigo")   // インディゴブルー
let turquoise: WebColor = .code("#40E0D0") // ターコイズ
let textColor = WebColor = .black          // 黒

タプル型の構造に記述される情報を以下では付加情報、またはペイロードと呼びます。
共用型の定義には、実体型を記述することはできません。
値型共用型の列挙型は別のものとして記述しなくてはなりません。

前回のシンプルな列挙型値型の列挙型では要素同士を比較することができましたが、共用型の列挙型の場合、自分で==演算子を定義するか、プロトコルEqutableを採用しない限り、相互に比較はできません

if turquoise == WebColor.code("#40E0D0")  // エラー。比較できない

もう少し複雑な例を示します。
次の例では、市営地下鉄で販売されている数種類の切符やプリペイドカードのどれもが、自動改札機で処理できるとし、切符やカードの種類を列挙型で定義しました。

enum Ticket {
    case 切符(Int, Bool, 回数券:Bool)  // 普通券:価格、小人、回数券かどうか
    case カード(Int, Bool)             // プリペイドカード:残高、小人
    case 敬老バス                      // 敬老バス
}

caseに記述する括弧の中はタプルで、項目にキーワードを付けることもできます。
このような列挙型に対する処理を書き分けるために、switch文を使います。
caseで定数や変数へ値を代入でき、付加情報を使わない場合には、下の「.カード」のように括弧以降を記述しないこともできます

以下では、caseの後に置かれる構造に関する条件の記述caseパターンと呼びます。
また、where以下に条件を書くことができるのはタプルを扱う場合と同じです。
次の例を確認してください。

switch t {
case let .切符(fare, flag, _):
    print("普通券: \(fare) " + (flag ? "小人" : "大人"))
case .敬老バス:
    print("敬老バス")
case .カード(let r, true) where r < 110:  // 小人の最低運賃
    print("カード: 残高不足")
case .カード(let r, false) where r < 230: // 大人の最低運賃
    print("カード: 残高不足")
case .カード:
    print("カード")

タプルをswitch文で使う場合と同様、付加情報を定数に代入するにはletを()の中に置く方法と外側に置く方法が使えます。

case let .切符(fare, flag, _):     // または
case .切符(let fare, let flag, _):

キーワード付きのタプルの場合、caseパターン内にはラベルを書かなくて大丈夫ですが、書くこともできます。
letとともに使う場合、letを書く場所に注意が必要です。

case let .切符(fare, _, 回数券: flag):     // または
case .切符(let fare, _, 回数券: let flag):

###3.if-case文について

与えられたタプルが、複数の条件のどれに一致するかを調べるには、switch文が便利に利用できました。
しかし、特定の1つの条件のみに一致するかどうか調べたいという場合もあります。

例えば、先ほどの例で用いたTicket型の変数pが、プリペイドカードかどうかだけ調べたいとします。しかし、if文で次のようには記述できません。

if p == .カード {              // エラー。この記述はできない
    print("プリペイドカード")
}

switch文なら次のように記述できますが、使わないとわかっているdefault節も書かなければならず、少々面倒です。

switch p {
case .カード: print("プリペイドカード")
default: break
}

そこで、こういった場合でも容易に記述できる構文が用意されています。
上の例は次のように記述できます。この構文をif-case文と呼ぶことがあります。

if case .カード = p {
    print("プリペイドカード")

条件はまずcaseパターンを記述し、「=」と対象となる式を置きます。
caseパターンと式が一致するかどうかは、switch文の場合と同様に処理されます。
switch文の場合と異なり、付加的な条件はwhere節ではなく、カンマ「,」で区切って後ろに続けます。

この記法はif文、while文、およびguard文の条件の書き方で説明したものと同じで、カンマで区切って、一般の式、オプショナル束縛構文などを記述可能です。

if caseパターン = 式, 条件 {.....}

次の例では、変数tの内容が残額が1200円以上のプリペイドカードかどうかを調べます。

if case .カード(let y, _) = t, y >= 1200 { .....

少し複雑な例として、辞書のインスタンスからTicket型の要素を取り出し、その情報がパターンに一致しているかを調べるという条件を記述してみましょう。

let tickets:[String:Ticket] = [ "志倉" : .切符(260, false, 回数券: true), 
               "佐々木" : .切符(220, false, 回数券:false)]
let name = "佐々木"
if let t = tickets[name], case .切符(220, _, _) = t {
    print("220円券")
}

この例の場合、辞書から得た情報がオプショナル型なので、まずオプショナル束縛構文で定数tにTicket型のインスタンスを得ます。
nilの場合はそこでifの条件が不成立となりますが、nilでなければ、次に定数tが220円の切符かどうか調べます。

なお、上のif文は「?」を使うと次のように記述することもできます。

if case .切符(220, _, _)? = tickets[name] {
    print("220円券")
}

###4.for-in文でcaseパターンを使う

for-in文でも、caseパターンを利用できます。
これまでに説明したfor-in文と同様に、配列などのSequenceプロトコルに適合した式をinの次に置き、インスタンスを次々に取り出します。

この書き方では、取り出されたインスタンスとcaseの次のパターンが一致した場合にだけ、コードブロックを実行します。
実行のための条件をさらに記述する必要があれば、where節を追加することもできます。

for case パターン in 式 where 式 {
    文... ...
}

たとえば、Ticket型の要素を持つ配列passesから次々にインスタンスを取り出して、220円より高い切符の情報を表示するには次のようにします。

for case let .切符(fare. child, coupon) in passes where fare > 220 {
    var k = coupon ? "回数券" : "普通券"
    if child { k += "(小人)" }
    print(k, "\(fare)円")
}

この文は次のように記述するのと同じです。

for t in passes {
    switch t {
    case let .切符(fare, child, coupon):
        if fare > 220 {
            var k = coupon ? "回数券" : "普通券"
            if child { k += "(小人)" }
            print(k, "\(fare)円")
        }
    default: break
    }
}

この構文でオプショナル型に当てはまる「?」を利用すると、オプショナル型が含まれるデータ列をうまく扱うことができます。
また、letではなく、varを使うこともできます。

###5.再帰的な列挙型

共用型の列挙型で、タプルの内部に自分自身を要素として指定したい場合がありえます。
例えば次のような場合を考えてみます。

enum メッセージ {
    case 文書(String, String)     // 差出人、文書
    case データ(String, [Int8])   // 差出人、データ列
    case 転送(String, メッセージ)  // 差出人、メッセージ
}

このデータの宣言は間違っていないようにも見えますが、Swiftでは扱うことができません。

Swiftでは列挙型値型のデータですので、直感的に説明するならば、負荷情報は列挙型のデータを表すメモリ位置に直接並べて格納されています。
上記のように自分自身を含む、つまり再帰的なデータ構造はデータを格納するために必要なメモリ容量がコンパイル時に決定することができません

これに対して参照型のデータは、メモリ上のどこかに存在する実体を間接的に参照するためのための情報なので、メモリの問題はありません。

しかし、再帰的な列挙型が使えると便利な場面も多いので、Swiftでは列挙型自分自身を付加情報に含む場合、該当するcaseの前にindirectというキーワードを記述します
これを間接指定と呼びます。indirect、つまり間接であるとは、その部分だけポインタのように間接参照を行うことを意味します。

今の話を以下の例に示します。
CustomStringConvertibleプロトコルを採用し、printで表示できるようにしています。

enum メッセージ : CustomStringConvertible {
    case 文書(String, String)              // 差出人、文書
    case データ(String, [Int8])            // 差出人、データ列
    indirect case 転送(String, メッセージ)  // 差出人、メッセージ
    
    ver description: String {
        switch self {
        case let .文書(from, str): return from + "(" + str + ")"
        case let .データ(from, _): return from + "[データ]"
        case let .転送(from, msg): return from + "←\(msg)"
        }
    }
}

実行例を示します。

let m1 = メッセージ.文書("伊藤", "休みます")
let m2 = メッセージ.転送("白石", m1)
let m3 = メッセージ.転送("山田", m2)
print(m3)   // "山田←白石←伊藤(休みます)"を出力

再帰的なcaseパターンがいくつかある場合には、enumの前にindirectを置いて全体に間接指定を行うことができます。

###6.おわりに

今回は共用型の列挙型についての記事を書きましたが、プロトコルやオプショナル型の扱いについては、記事が少し長くなってしまうので今回は解説していません。
来週以降はクラスなどについての解説をしていこうと思います。
ここまで読んでくれた方、ありがとうございました。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?