概要
初心者向けの記事です。初心者でなくてもなんらかの発見があるかもしれないです。
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
を忘れずに!