取りうる状態が特定の集合に限られているデータをモデル化するとき、たとえば曜日やコンパスの方位、コントローラーのボタンなどを扱う場合、IntやStringのような基本的な型には大きな欠点があります。
整数や文字列を使うと、コードは不正な値(999というステータスコードや、"leftt"のようなタイポなど)を常にチェックしなければなりませんし、人間の読者もその数値が何を表しているのかすぐには分かりません。
Swiftはこの問題を、列挙型(しばしばenumと略されます)で解決します。 列挙型は、関連する名前付きの値の集まりに対して共通の型を定義します。コンパイラーは取りうるすべてのケースを事前に把握しているので、コンパイル時にコードの正確さと網羅性をチェックできます。 列挙型は、メソッドやイニシャライザー、計算プロパティーを持つこともできます。
列挙型は、enumキーワードのあとに型名を書き、波括弧の中にケースを並べて定義します。
Swiftの慣習では、型名はUpperCamelCase、各ケースはlowerCamelCaseを使います。
enum NESButton {
case up
case down
case left
case right
case a
case b
case select
case start
}
複数のケースをカンマで区切って1行にまとめて定義することもできます。
enum NESButton {
case up, down, left, right, a, b, select, start
}
列挙型のケースを参照するには、列挙型の型名、ドット(.)、ケース名を書きます。型が判明している場合、またはコンパイラーが推論できる場合は、型名を省略して短いドット構文を使えます。
var lastPressed = NESButton.up
// Swift infers the type, so you can omit the type name:
lastPressed = .down
let konamiCode: [NESButton] = [.up, .up, .down, .down, .left, .right, .left, .right, .b, .a]
列挙型は、クラスや構造体と同じように、メソッドや独自のイニシャライザーを定義できます。列挙型のメソッドの中で、selfは現在のケースの値を指します。
enum Coin {
case heads
case tails
init(_ value: Int) {
if value.isMultiple(of: 2) {
self = .heads
} else {
self = .tails
}
}
func flip() -> Coin {
switch self {
case .heads: return .tails
case .tails: return .heads
}
}
}
let tails = Coin(13) // .tails
let heads = Coin(0) // .heads
let flipped = heads.flip() // .tails
列挙型のケースには、生の値と呼ばれるデフォルト値をあらかじめ設定できます。
列挙型の生の値は、すべて同じ型(Int、String、Characterなど)でなければなりません。
enum NESButton: Character {
case up = "⬆️"
case down = "⬇️"
case left = "⬅️"
case right = "➡️"
case a = "🅰️"
case b = "🅱️"
case select = "✅"
case start = "🚦"
}
print(NESButton.left.rawValue) // Prints "⬅️"
init(rawValue:)を使うと、生の値から列挙型を初期化できます。無効な生の値が渡される可能性があるため、このイニシャライザーはオプショナルを返します。
let upButton = NESButton(rawValue: "⬆️") // Optional(NESButton.up)
let invalid = NESButton(rawValue: "🙄") // nil
Swiftは、StringとIntの列挙型に対して生の値を自動で割り当てることができます。
Stringの列挙型では、各ケースの生の値はデフォルトでそのケース名になります。Intの列挙型では、特に指定しない限り、生の値は0から始まり1ずつ増えていきます。enum Planet: Int {
case mercury = 1, venus, earth, mars
}
print(Planet.earth.rawValue) // Prints 3
この演習では、ログ行を処理します。
各ログ行は、次の形式の文字列です:"[<LVL>]: <MESSAGE>"。
ログレベルには次のものがあります。
TRC(trace)DBG(debug)INF(info)WRN(warning)ERR(error)FTL(fatal)必要な機能を実装するには、3つの課題をすべて完了する必要があります。
LogLevel enumを作成します上記のログレベルに対応する6つのケースと、ログレベルが欠けている場合や標準的でないログレベルのためのunknownケースを持つLogLevel enumを定義します。
tracedebuginfowarningerrorfatalunknown次に、ログ行のログレベルを解析するLogLevelのイニシャライザーを実装します。
LogLevel("[INF]: File deleted")
// Returns LogLevel.info
LogLevel("Something went wrong!")
// Returns LogLevel.unknown
ログ行のログレベルはかなり冗長です。
ログ行を保存するのに必要なディスク容量を減らすため、次のような短い形式が考案されました。"<ENCODED_LEVEL>:<MESSAGE>"
エンコードされたログレベルは、ログレベルを数値に単純に対応付けたものです。
trace - 0
debug - 1
info - 4
warning - 5
error - 6
fatal - 7
unknown - 42
短い形式のログ行をStringとして出力するshortFormat(message: String) -> Stringメソッドを実装します。
let overflow = LogLevel.error
overflow.shortFormat(message: "Stack overflow")
// Returns "6:Stack overflow"