391 Views
December 06, 14
スライド概要
Swift の新機能『値付き列挙型』について、列挙型の基礎から、Swift 自身での応用例まで徹底的に掘り下げてみました。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 8,323 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
値付き enum 入門 〜 そして伝説へ 〜 2014.11.29 @ 横浜へなちょこiOS勉強会 #34.1 2014.12.06 @ 第59回 Cocoa 勉強会関西 EZ-NET 熊谷友宏 http://ez-net.jp/
自己紹介 EZ-NET 熊谷友宏 @es̲kumagai Xcode 5 徹底解説 IP Phone 音でダイヤル 音で再配達ゴッド いつもの電卓 for iPhone いつもの電卓 for iPad 音で再配達
列挙型 ― いよいよ enum は究極形態へ ―
列挙型 普通の列挙型
普通の列挙型 列挙型 定義 Swift enum Basket { case Empty case Pineapple case Melon } 1. 限られた候補の中から値を採るデータ型 2. 意味で値をひとまとめにできる 3. 値を言葉で扱えるのでコードが明瞭に
普通の列挙型 列挙型 使い方 Swift let value:Basket switch value { case .Pineapple: … case .Melon: … case .Empty: … }
列挙型 やや新しい列挙型
やや新しい列挙型 列挙型 Raw 値の割り当て 定義 enum Basket : Int { case Empty = 0 case Pineapple = 1 case Melon = 2 } 1. 列挙子に内部的な値を付与 2. Objective-C では必ず Raw 値が設定される 3. Swift で Raw 値がない列挙子は具体値なし
やや新しい列挙型 列挙型 列挙子から Raw 値を取得 Raw 値を取得 let raw = Basket.Melon.rawValue Raw 値から列挙子を生成 列挙子を生成 let value = Basket(rawValue: raw)!
列挙型 まったく新しい列挙型
まったく新しい列挙型 列挙型の新機能 1. 列挙型もオブジェクト プロパティやメソッドを実装可能 2. 整数値以外を Raw 値で使える リテラル値から変換できる型を利用可能 3. 列挙子と合わせて値を持てる 複数の値を自由に保有可能 ? ?
まったく新しい列挙型 1. 列挙型もオブジェクト
まったく新しい列挙型 プロパティやメソッドを実装可能
まったく新しい列挙型
列挙子を文字列に変換するメソッド
メソッドの実装
enum Basket {
case Empty, Pineapple, Melon
func toString() -> String? {
switch self {
case .Empty:
return nil
case .Pineapple:
return "Pineapple"
case .Melon:
return "Melon"
}
}
まったく新しい列挙型 列挙型の変数からメソッドを実行したり 変数から実行 let basket = Basket.Melon let string = basket.toString() 列挙子から直接メソッドを実行したり 列挙子から実行 let string = Basket.Melon.toString()
まったく新しい列挙型 ? 2. 整数値以外を Raw 値で使える
まったく新しい列挙型 実際に使ってみると…
まったく新しい列挙型 エラー 『Raw 型がどのリテラルからも変換できません』
まったく新しい列挙型 リテラル値から変換できる型を 利用できるかと思えば …
まったく新しい列挙型 利用できないものが多い
まったく新しい列挙型 よくあるリテラル 1. 整数値リテラル … 0 [OK] IntegerLiteralConvertible 2. 浮動小数点数値リテラル … 0.0 [OK] FloatLiteralConvertible 3. 真偽値リテラル … true [NG] BooleanLiteralConvertible 4. 文字列リテラル … "STRING" StringLiteralConvertible [OK]
まったく新しい列挙型 これもリテラル 1. nil リテラル … nil [NG] NilLiteralConvertible 2. 配列リテラル … [ value, ... ] [NG] ArrayLiteralConvertible 3. 辞書リテラル … [ key : value, ... ] [NG] DictionaryLiteralConvertible
まったく新しい列挙型 使えるものに注目すれば … 1. 整数値リテラル 2. 浮動小数点数値リテラル 3. 文字列リテラル
まったく新しい列挙型 列挙子の値が… 文字列 でも良い
まったく新しい列挙型 Raw 値を文字列型にする… 定義 enum Basket : String { case Empty = "" case Pineapple = "Pineapple" case Melon = "Melon" } 1. 内部的な値を文字列で付与 2. 列挙子と文字列値とが関連付けられる
まったく新しい列挙型 Raw 値として文字列を取り出せる Raw 値の取得 let string:String = Basket.Melon.rawValue 文字列から列挙子を生成することも可能 列挙子の生成 let value:Basket? = Basket(rawValue: "Melon")
まったく新しい列挙型 列挙子の型が… リテラルから変換 できれば良い 対応する ― それと等価判定もできること ―
まったく新しい列挙型 対応するリテラル 1. 整数値リテラル IntegerLiteralConvertible 2. 浮動小数点数値リテラル FloatLiteralConvertible 3. 文字列リテラル StringLiteralConvertible ― 等価判定は Equatable ―
まったく新しい列挙型 対応リテラルからの変換機能を実装 クラスを浮動小数点数値リテラルに対応させる プロトコルへ準拠 class MyClass : FloatLiteralConvertible, Equatable { // 変換イニシャライザ(浮動小数点数から) required init(floatLiteral value: FloatLiteralType) { } } // 等価演算子 func ==(lhs:MyClass, rhs:MyClass) -> Bool { return true }
まったく新しい列挙型 列挙型で独自クラスを使用 浮動小数点数値リテラルに対応したクラス 列挙型の定義 enum Basket : MyClass { case Empty = 0.0 case Pineapple = 1.0 case Melon = 1.1 } 1. Raw 値の型に独自クラスを使用可能 2. Raw 値はリテラルで指定
扱い方は普段どおり 普通に使う分には Raw 値は意識しない 列挙型の使用 let basket:Basket = .pineapple switch basket { case .Pineapple: … case .Melon: … case .Empty: … }
まったく新しい列挙型 Raw 値の独自型は い つ 何時、使われるのか
まったく新しい列挙型 要点1 列挙子を使う分には… 1. 独自型は 全く使われない 2. 列挙子の操作では リテラル が使われる 使用 let basket:Basket = .pineapple switch basket { case .Pineapple: … case .Melon: … }
まったく新しい列挙型 要点2 Raw 値を取得したとき… 1. 初めてインスタンスが生成される 2. 変換イニシャライザ が実行される 3. 列挙子が対応するリテラルが渡される Basket.Melon.rawValue MyClass(floatLiteral: 1.1) MyClass
まったく新しい列挙型 要点3 Raw 値から列挙型を生成するとき… 1. Raw 値から インスタンスを生成 2. 列挙子を 順次 インスタンス化して比較 3. 戻り値は MyEnum? 型 4. 該当しない場合は nil を返す
let obj = MyClass(floatLiteral: 1.0) MyEnum(rawValue: obj) case .Empty MyClass(floatLiteral: 0.0) == obj case .Pineapple MyClass(floatLiteral: 1.0) == obj MyEnum? case .Melon MyClass(floatLiteral: 1.1) == obj
まったく新しい列挙型 要点4 リテラルから列挙子を作るときも… 1. いったん 独自型に変換 される 2. 列挙子を 順次 インスタンス化して比較 MyEnum(rawValue: 1.0) MyEnum(rawValue: MyClass(floatLiteral: 1.1)) MyEnum?
まったく新しい列挙型 つまり独自型の Raw 値は… 1. 使い出すとハイコスト その都度インスタンス化が行われる 2. 使わなければリテラルと同等 列挙子自体は独自型では管理されない ― 列挙子とインスタンスを関連付ける的な役割 ―
まったく新しい列挙型 ? 3. 列挙子と合わせて値を持てる
値付き列挙型 associated values ― 値を持てる列挙型 ―
値付き列挙型 値付き列挙型 定義 Swift enum Basket { case Empty case Fruit(String) case Animal(String) } ― 列挙子にデータ型を添えて定義 ―
値付き列挙型 値付き列挙型 列挙子を使う 宣言と代入 let basket1 = Basket.Fruit("りんご") let basket2 = Basket.Animal("ライオン") let basket3 = Basket.Empty ― 値付き列挙子には値を必ず添える ―
値付き列挙型 値付き列挙型 値を加味した篩い分け 分岐 let basket:Basket switch basket { case .Fruit("みかん"): … case .Fruit("りんご"): … default: … } ― 列挙子と値での篩い分けが可能 ―
値付き列挙型 値付き列挙型 大まかな篩い分け 分岐 let basket:Basket switch basket { case .Fruit: … case .Animal: … case .Empty: … } ― 列挙子だけでの篩い分けも可能 ―
値付き列挙型 値付き列挙型 値を加味したり、しなかったり 分岐 let basket:Basket switch basket { case .Fruit("みかん"): … case .Fruit: … default: … } ― 自由に混在可能 ―
値付き列挙型 値付き列挙型 値を取り出して利用する 分岐・値の取得 let basket:Basket switch basket { case let .Fruit(fruit): println("\(fruit)!") } ― 列挙子が一致したとき、その値を取り出す ―
値付き列挙型 複数の値も付けられる
値付き列挙型 値が複数付いた列挙型 定義 Swift enum Basket { case Empty case Fruit(String, Int) case Animal(String, Int, Bool) } ― タプル型で列挙子の値を定義 ―
値付き列挙型 値が複数付いた列挙型 値を取り出して利用する 分岐と各値の判定・取得 let basket:Basket switch basket { case let .Fruit("りんご", price): println("\りんごは特売! \(price)円!") case let .Fruit(name, price): println("\(name)が\(price)円!") } ― 値を個別に取り出せる、どれか固定も可能 ―
値付き列挙型 値が複数付いた列挙型 値を一括で取り出すことも可能 分岐と値の一括取得 let basket:Basket switch basket { case let .Fruit(value): println("\(value.0)が\(value.1)円!") } ― 値をタプル型で取り出せる ―
値付き列挙型 値が複数付いた列挙型 名前付きタプルも利用可能 定義 enum Basket { case Empty case Fruit(name: String, price: Int) } 使用 switch basket { case let .Fruit(value): println("\(value.name)が\(value.price)円!") }
ジェネリックな 値付き列挙型 ― 汎用的な値を持つ ―
ジェネリックな値付き列挙型
ジェネリックとは
Generics
汎用的に型を扱う仕組み
― 型に縛られないコードが書ける ―
ジェネリック関数
func makePair<T,U>(first:T, second:U)->(T,U) {
return (first, second)
}
ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 定義 Swift enum Basket<Type> { case Empty case Fruit(Type) case Animal(String) } 上 G N 様 仕 ジェネリックを値に持つ列挙型で 複数の値付き列挙子を定義できない
ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 定義 Swift enum Basket<Type> { case Empty case Nandemo(Type) } ― 列挙子が採る値の型を汎用化 ―
ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 型に縛られない列挙型の使用 宣言と代入 let basket1:Basket<String> = Basket.Nandemo("りんご") let basket2:Basket<Int> = Basket.Nandemo(1000) 1. 渡した値に応じて列挙型が決まる 2. 宣言以降は値の型を変えられない 3. あくまでもコードの汎用化が目的
ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 扱い方は通常と同じ 判定 switch basket { case let .Nandemo(value): … case .Empty: … } ― 値の型は宣言時に決めた型 ―
ジェネリックな値付き列挙型 ところで…
ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 似たコンセプトの列挙型… この列挙型と、 enum Basket<Type> { case Empty case Nandemo(Type) } この列挙型と。 enum Optional<T> { case None case Some(T) }
ジェネリックな値付き列挙型 どこかで見覚えのある列挙型 enum Optional<T> { } case None case Some(T)
そして伝説へ…
そして伝説へ… Optional<T> ― nil を持てる型 ―
そして伝説へ… Optional<T> オプショナルとは? 1. 任意の値を格納できる型 2. 値の型は宣言時に決定 3. 値がない状態を “nil” で表現可能 4. Swift 言語の重要な機能
そして伝説へ… Swift の Optional って String?
シンタックスシュガー ― 簡単に書くための構文 ―
シンタックスシュガー String? is Optional<String> ― 明示的な扱いが必要な nil 許容型 ―
そして伝説へ… Optional<T> Swift で定義されている
そして伝説へ… Optional<T> 定義から見る特徴 1. ジェネリックな値付き列挙型 2. ある値 .Some(T) と 何もない値 .None を採る 3. 引数名なしで値を採るイニシャライザ init(̲ some:T) 4. nil リテラルから変換可能 NilLiteralConvertible
ジェネリックな値付き列挙型 シンタックスシュガー ということは
シンタックスシュガー この2つは同じことを表現 嘘のような本当の話 シンタックスシュガー let str1:String? = "STRING" let str1:String? = nil 列挙型による実装 let str2:Optional<String> = .Some("STRING") let str2:Optional<String> = .None
シンタックスシュガー 同じことなので混在も可能 表現は違っても値は同じ シンタックスシュガーと列挙型の混在 // Optional<T> に T? のときと同じ表現で代入 let str1s:Optional<String> = "STRING" let str1n:Optional<String> = nil // String? に enum Optional<T> の書式で代入 let str2s:String? = .Some("STRING") let str2n:String? = .None // String? を Optional<T> のイニシャライザで生成 let str3s:String? = Optional<String>("STRING") let str3n:String? = Optional<String>()
シンタックスシュガー 実際の動きを知ると Optional が分かりやすくなる
シンタックスシュガー 値を Optional でラップする Optional 型に値を設定 シンタックスシュガー // 型の最後に「?」を付ける let str:String? = "STRING" 列挙型による実装 // 列挙型 Optional.Some の値として指定する let str2 = Optional.Some("STRING") // Optional<T> のイニシャライザでも良い let str2 = Optional("STRING")
シンタックスシュガー Optional に nil を代入する Optional 型に nil を設定 シンタックスシュガー // 型の最後に「?」を付けた変数に nil を代入する let str:String? = nil 列挙型による実装 // Optional<T>.None を代入する(要・型の明示) let str2 = Optional<String>.None // Optional<T> のイニシャライザでも良い let str2 = Optional<String>()
シンタックスシュガー 値が nil かを判定する 値があるかないかを if で判定 シンタックスシュガー if str != nil { } else { } 列挙型による実装 switch str { case .Some: … case .None: … }
シンタックスシュガー 値が nil かを判定する Optional Binding で値を取得 シンタックスシュガー if let value = str { } else { } 列挙型による実装 switch str { case let .Some(value): … case .None: … }
シンタックスシュガー Optional から値を取り出す 強制アンラップ シンタックスシュガー let value = str! 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: abort() }
シンタックスシュガー Optional から値を取り出す nil 結合演算子(右辺が nil 非許容のとき) シンタックスシュガー let value = str ?? "DEFAULT" 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: value = "DEFAULT" }
シンタックスシュガー Optional から値を取り出す nil 結合演算子(右辺が nil 許容のとき) シンタックスシュガー let value = str ?? Optional("DEFAULT") 列挙型による実装 var value:String? switch str { case let .Some: value = str! case .None: value = Optional("DEFAULT") }
シンタックスシュガー Optional から値を取り出す Optional Chaining で値を取得 シンタックスシュガー let value = str?.hashValue 列挙型による実装 var value:Int? switch str { case let .Some(v): value = v.hashValue case .None: value = nil }
! も ? も ?? も バリエーション nil かどうかで「どうするか」 が少し違うだけ
シンタックスシュガー シンタックスシュガーのおかげで Optional を簡単に扱える ― switch 文を省略できる ―
ちなみに
Optional<T> には func map<U>(f: (T)->U) -> U? 1. 値が nil なら nil を返す 2. 値が nil でなければ、引数で渡された クロージャの実行結果をラップして返す
シンタックスシュガー
Optional から値を取り出す
Optional<T>.map<U> メソッドで実行
シンタックスシュガー
let value = str?.hashValue
Optional 型による実装
// いちばん短い書き方
let value = str.map { $0.hashValue }
// 省略しない書き方
let value = str.map({ (value:String) -> Int in
})
return value.hashValue
もうひとつ
ImplicitlyUnwrappedOptional<T> ― Optional の姉妹品 ―
シンタックスシュガー String! is ImplicitlyUnwrappedOptional<String> ― 暗黙的にアンラップされる nil 許容型 ―
シンタックスシュガー ImplicitlyUnwrappedOptional 値のラップは Optional と同等 シンタックスシュガー // 型の最後に「!」を付ける let str:String! = "STRING" 列挙型による実装 // 列挙型 .Some の値として指定する let str2 = ImplicitlyUnwrappedOptional.Some("STRING")
シンタックスシュガー ImplicitlyUnwrappedOptional nil の設定は Optional と同等 シンタックスシュガー // 型の最後に「!」を付けた変数に nil を代入する let str:String! = nil 列挙型による実装 // .None を代入する(要・型の明示) let str2 = ImplicitlyUnwrappedOptional<String>.None
シンタックスシュガー ImplicitlyUnwrappedOptional 値の nil 判定は Optional と同等 シンタックスシュガー if str != nil { } else { } 列挙型による実装 switch str { case .Some: … case .None: … }
シンタックスシュガー ImplicitlyUnwrappedOptional Optional Binding は存在しない ( if let v = str ) ― 暗黙アンラップが基本 ―
シンタックスシュガー ImplicitlyUnwrappedOptional アンラップは型が明確なら暗黙的に実施 シンタックスシュガー let value = str! let value:String = str 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: abort() } // 明示アンラップ // 暗黙アンラップ
シンタックスシュガー ImplicitlyUnwrappedOptional nil 結合演算子は Optional と同等(nil 非許容) シンタックスシュガー let value = str ?? "DEFAULT" 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: value = "DEFAULT" }
シンタックスシュガー
ImplicitlyUnwrappedOptional
nil 結合演算子は Optional と同等(nil 許容)
シンタックスシュガー
// 結果は Optional<T> で得られる
let value = str ?? Optional("DEFAULT")
列挙型による実装
var value:String?
switch str {
case let .Some:
value = str!
case .None:
value = Optional("DEFAULT")
}
シンタックスシュガー ImplicitlyUnwrappedOptional Optional Chaining は無くて暗黙アンラップ シンタックスシュガー //「?」はなく即時アンラップ(nil なら強制終了) let value = str.hashValue 列挙型による実装 var value:Int switch str { case let .Some(v): value = v.hashValue case .None: abort() }
シンタックスシュガー ImplicitlyUnwrappedOptional も Optional と僅かに違うだけ 1. 明示的/暗黙的アンラップ str! 2. Optional Binding の有無 if let v = str 3. Optional Chaining の有無 str?.hashValue
仕組みを意識すると Optional って意外とシンプル ― 列挙型のシンタックスシュガー ―
以上 値付き enum 入門 列挙型の基礎から Swift での実用例までのお話でした
『値付き enum 入門』 1. 普通な列挙型 case Pineapple, Melon, Empty 2. Raw 値を持てる列挙型 enum Basket : MyClass { 3. 列挙子ごとに値を持てる列挙型 case Fruit(String) 4. ジェネリックな値付き列挙型 enum Basket<T> { 5. Optional も列挙型 enum Optional<T> {