8
7

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 enum型を復習する

Posted at

概要

初心者向けの記事です。初心者でなくてもなんらかの発見があるかもしれないです。
Swiftの文法のenumについて書きます。

動作はSwift5.3で確認

最初の問題

SwiftのenumでRawValueとAssociatedValueは同時に利用可能か?

これがわかった人は、特にこの記事を読む必要がないかもです。

enum

列挙型と言う。
enumとcaseで定義します。

enum CompassPoint {
    case north
    case south
    case east
    case west
}

変数に格納

変数は単数形でかく。
変数がどのenum型かわかれば.eastのように型を省略できる。

var directionToHead = CompassPoint.north
directionToHead = .south

var directionToHeadLast: CompassPoint = .east

Switch

Switch文では、enumの全てのcaseを網羅しないとコンパイルエラー。
defaultを使うことも可能。

switch directionToHead {
case .north, .east, .south, .west: print("direction")
}

switch directionToHead {
case .north, .east, .south: print("direction")
default: print("other")
}

イテレーションを回す

CaseIterableをつけることでenumの要素をfor文などで回せます。

こんな感じです


enum CompassPoint: CaseIterable {
    case north
    case south
    case east
    case west
}


CompassPoint.allCases.forEach{print($0)}

これは以下が出力されます。

north
south
east
west

Associated Values

先ほどのenumの各要素になんらかの値を持たせることができるものです。


enum Appliance {
    case light(isOn: Bool)
    case airCon(degree: Int, isOn: Bool)
}

var appliance: Appliance
appliance = .light(isOn: true)
appliance = .airCon(degree: 28, isOn: true)

Switch文で使うときはletやvarでAssociated Valueを取り出せます。

switch appliance {
case .light(let isOn):
    print(isOn)
case .airCon(let degree, let isOn):
    print(isOn, degree)
}

先ほど出たCaseIterableはAssociated Valuesのあるenumには使えないので注意!

ちょい技

associated valueが全部letだったり全部varの時は次のようにかけます。


switch appliance {
case let .light(isOn):
    print(isOn)
case let .airCon(degree, isOn):
    print(isOn, degree)
}

letを前に出して、letを複数個書かなくて良くなる!

Raw Values

Associated Valuesと違って、書いた時点で値の決まっている値をenumの各caseに入れられます。

enum Step: Int {
    case one = 1
    case two = 2
    case three = 3
}

print(Step.three.rawValue)

これは3が表示されます。

enum Step: Int {
    case one, two, three
}

print(Step.one.rawValue)

これは何が表示されると思いますか?

実は

0

が表示されます。

IntもしくはStringのRaw Valueの時だけ、勝手に値が入ります。
Intの場合は、0が初めのcaseに入り1つずつインクリメントされます。
Stringの場合は、caseの名前そのものが入ります。

さらに研究

RawValueについてさらにいろいろやってみましょう。
問題を出すので、出力される文字列を当ててみましょう。

Q1

enum Step: Int,CaseIterable {
    case one, two, three=99
}

Step.allCases.forEach{print($0.rawValue)}
Q1答え
0
1
99

Q2


enum Step: Int,CaseIterable {
    case one, two=99, three
}

Step.allCases.forEach{print($0.rawValue)}
Q2答え
0
99
100

Q3


enum Step: Int,CaseIterable {
    case one=12, two=11, three
}

Step.allCases.forEach{print($0.rawValue)}
Q3答え

コンパイルエラーです。
threeはtwoの11をインクリメントした12が入るのですが、これがoneとかぶるので怒られます。

Q4


enum Step: Int8,CaseIterable {
    case one, two=127, three
}

Step.allCases.forEach{print($0.rawValue)}

Q4答え

これもコンパイルエラー
Int8のmaxは127なので、threeの値がInt8のオーバーフローすると怒られます。

error: MyPlayground.playground:12:24: error: integer literal '128' overflows when stored into 'Int8'
    case one, two=127, three

RawValueで初期化

rawValueからenumにすることができます。
enumにない値を指定すると、nilになるので注意!


enum Step: Int, CaseIterable {
    case one=1, two, three
}

Step(rawValue: 2) // two
Step(rawValue: 4) // nil

つまりrawValueで初期化したときに返り値は、Optionalになります。
rawValueでの初期化を行うときには、Optional Bindingを使いましょう

guard let step = Step(rawValue: 4) else {
    fatalError("undefined!!")
}

SwiftのenumでRawValueとAssociatedValueは同時に利用可能か?

最初の問題ですね。

答えは、できる

普通にやった場合は、こちらのコード


enum CompassPoint: String {
    case north(Int)
    case south(Int)
    case east(Int)
    case west(Int)
}

コンパイルエラーとなってしまいます。

'CompassPoint' declares raw type 'String', but does not conform to RawRepresentable and conformance could not be synthesized

しかし、実はなんとかなる。

今回説明したenumのRawValueの書き方は、shorthand notationといいます。以下のような書き方です。


enum Test: String {
    case pass = "やったー!"
}

RawValueを実現する条件は、上のコンパイルエラーにもありますが、RawRepresentableというプロトコルへの準拠です。
caseにAssociatedValueが1つでもあると、shorthand notationではRawRepresentableには自動的に準拠しなくなってしまいます。
なので、以下のようにすればRawValueとAssociatedValueは同時に利用できます。

enum TestResult {
    case pass(Int)
    case fail(Int)
}

extension TestResult: RawRepresentable {
    typealias RawValue = String
    
    init?(rawValue: RawValue) {
        switch rawValue {
        case "やったー!": self = .pass(1)
        case "残念、、": self = .fail(0)
        default:
            return nil
        }
    }
    
    var rawValue: RawValue {
        switch self {
        case .pass: return "やったー!"
        case .fail: return "残念、、"
            
        }
    }
}

TestResult.pass(1).rawValue // やったー!
TestResult.fail(0).rawValue // 残念、、

注意点は、initの時にAssociatedValueを入れなければいけないことです。

結局shorthand notationのRawValueの書き方というのは、ただの短縮表現であり、コンパイラとしては上のようにRawRepresentableになるよう作っているようです。(参考: The RawRepresentable Protocol in Swift – Ole Begemann)

再帰のenum

再帰的にenumをAssociatedValueに使いたい場合には、indirectキーワードをそのcaseにつける必要があります。


enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression) // ここの引数に自分自身が入っているから再帰
}

indirectはenumの最初に持ってくることも可能です。

indirect enum ArithmeticExpression: CustomStringConvertible {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
}

indirectなしだと?

コンパイルエラー

error: MyPlayground.playground:2:6: error: recursive enum 'ArithmeticExpression' is not marked 'indirect'
enum ArithmeticExpression: CustomStringConvertible {
     ^
indirect 

ちょっと文字列表示やってみた

let expression = ArithmeticExpression.addition(.number(10), .addition(.number(1), .number(2)))

print(expression.description)

上のprintで式を文字列で表示したいです。
CustomStringConvertibleを使って、再起的に式の文字列を表示してみました。

enum ArithmeticExpression: CustomStringConvertible {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    
    var description: String {
        get {
            switch self {
            case .number(let num):
                return String(num)
            case .addition(let expr1, let expr2):
                return "\(expr1.description) + \(expr2.description)"
            }
        }
    }
}

こんな感じでしょうか?

10 + 1 + 2

無事表示されました!

まとめ

  • swiftのenumで覚えるべきなのはAssociated ValuesとRawValue
  • Associated Valuesはcaseに値を持たせられる
  • RawValueはcaseに対応した値を設定できる。
  • Associated ValuesとRawValueは同時に利用できる
  • 再帰のenumはindirectを忘れずに!

参考

8
7
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
8
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?