値付き Enum 入門、そして伝説へ #yhios #cocoa kansai

378 Views

December 06, 14

スライド概要

Swift の新機能『値付き列挙型』について、列挙型の基礎から、Swift 自身での応用例まで徹底的に掘り下げてみました。

※ Docswell での公開に移行する直前の Slideshare での閲覧数は 8,323 でした。

profile-image

正統派趣味人プログラマー。プログラミングとは幼馴染です。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

値付き enum 入門 〜 そして伝説へ 〜 2014.11.29 @ 横浜へなちょこiOS勉強会 #34.1 2014.12.06 @ 第59回 Cocoa 勉強会関西 EZ-NET 熊谷友宏 http://ez-net.jp/

2.

自己紹介 EZ-NET 熊谷友宏 @es̲kumagai Xcode 5 徹底解説 IP Phone 音でダイヤル 音で再配達ゴッド いつもの電卓 for iPhone いつもの電卓 for iPad 音で再配達

3.

列挙型 ― いよいよ enum は究極形態へ ―

4.

列挙型 普通の列挙型

5.

普通の列挙型 列挙型 定義 Swift enum Basket { case Empty case Pineapple case Melon } 1. 限られた候補の中から値を採るデータ型 2. 意味で値をひとまとめにできる 3. 値を言葉で扱えるのでコードが明瞭に

6.

普通の列挙型 列挙型 使い方 Swift let value:Basket switch value { case .Pineapple: … case .Melon: … case .Empty: … }

7.

列挙型 やや新しい列挙型

8.

やや新しい列挙型 列挙型 Raw 値の割り当て 定義 enum Basket : Int { case Empty = 0 case Pineapple = 1 case Melon = 2 } 1. 列挙子に内部的な値を付与 2. Objective-C では必ず Raw 値が設定される 3. Swift で Raw 値がない列挙子は具体値なし

9.

やや新しい列挙型 列挙型 列挙子から Raw 値を取得 Raw 値を取得 let raw = Basket.Melon.rawValue Raw 値から列挙子を生成 列挙子を生成 let value = Basket(rawValue: raw)!

10.

列挙型 まったく新しい列挙型

11.

まったく新しい列挙型 列挙型の新機能 1. 列挙型もオブジェクト プロパティやメソッドを実装可能 2. 整数値以外を Raw 値で使える リテラル値から変換できる型を利用可能 3. 列挙子と合わせて値を持てる 複数の値を自由に保有可能 ? ?

12.

まったく新しい列挙型 1. 列挙型もオブジェクト

13.

まったく新しい列挙型 プロパティやメソッドを実装可能

14.
[beta]
まったく新しい列挙型

列挙子を文字列に変換するメソッド
メソッドの実装

enum Basket {
case Empty, Pineapple, Melon
func toString() -> String? {
switch self {
case .Empty:
return nil
case .Pineapple:
return "Pineapple"
case .Melon:
return "Melon"
}
}

15.

まったく新しい列挙型 列挙型の変数からメソッドを実行したり 変数から実行 let basket = Basket.Melon let string = basket.toString() 列挙子から直接メソッドを実行したり 列挙子から実行 let string = Basket.Melon.toString()

16.

まったく新しい列挙型 ? 2. 整数値以外を Raw 値で使える

17.

まったく新しい列挙型 実際に使ってみると…

18.

まったく新しい列挙型 エラー 『Raw 型がどのリテラルからも変換できません』

19.

まったく新しい列挙型 リテラル値から変換できる型を 利用できるかと思えば …

20.

まったく新しい列挙型 利用できないものが多い

21.

まったく新しい列挙型 よくあるリテラル 1. 整数値リテラル … 0 [OK] IntegerLiteralConvertible 2. 浮動小数点数値リテラル … 0.0 [OK] FloatLiteralConvertible 3. 真偽値リテラル … true [NG] BooleanLiteralConvertible 4. 文字列リテラル … "STRING" StringLiteralConvertible [OK]

22.

まったく新しい列挙型 これもリテラル 1. nil リテラル … nil [NG] NilLiteralConvertible 2. 配列リテラル … [ value, ... ] [NG] ArrayLiteralConvertible 3. 辞書リテラル … [ key : value, ... ] [NG] DictionaryLiteralConvertible

23.

まったく新しい列挙型 使えるものに注目すれば … 1. 整数値リテラル 2. 浮動小数点数値リテラル 3. 文字列リテラル

24.

まったく新しい列挙型 列挙子の値が… 文字列 でも良い

25.

まったく新しい列挙型 Raw 値を文字列型にする… 定義 enum Basket : String { case Empty = "" case Pineapple = "Pineapple" case Melon = "Melon" } 1. 内部的な値を文字列で付与 2. 列挙子と文字列値とが関連付けられる

26.

まったく新しい列挙型 Raw 値として文字列を取り出せる Raw 値の取得 let string:String = Basket.Melon.rawValue 文字列から列挙子を生成することも可能 列挙子の生成 let value:Basket? = Basket(rawValue: "Melon")

27.

まったく新しい列挙型 列挙子の型が… リテラルから変換 できれば良い 対応する ― それと等価判定もできること ―

28.

まったく新しい列挙型 対応するリテラル 1. 整数値リテラル IntegerLiteralConvertible 2. 浮動小数点数値リテラル FloatLiteralConvertible 3. 文字列リテラル StringLiteralConvertible ― 等価判定は Equatable ―

29.

まったく新しい列挙型 対応リテラルからの変換機能を実装 クラスを浮動小数点数値リテラルに対応させる プロトコルへ準拠 class MyClass : FloatLiteralConvertible, Equatable { // 変換イニシャライザ(浮動小数点数から) required init(floatLiteral value: FloatLiteralType) { } } // 等価演算子 func ==(lhs:MyClass, rhs:MyClass) -> Bool { return true }

30.

まったく新しい列挙型 列挙型で独自クラスを使用 浮動小数点数値リテラルに対応したクラス 列挙型の定義 enum Basket : MyClass { case Empty = 0.0 case Pineapple = 1.0 case Melon = 1.1 } 1. Raw 値の型に独自クラスを使用可能 2. Raw 値はリテラルで指定

31.

扱い方は普段どおり 普通に使う分には Raw 値は意識しない 列挙型の使用 let basket:Basket = .pineapple switch basket { case .Pineapple: … case .Melon: … case .Empty: … }

32.

まったく新しい列挙型 Raw 値の独自型は い つ 何時、使われるのか

33.

まったく新しい列挙型 要点1 列挙子を使う分には… 1. 独自型は 全く使われない 2. 列挙子の操作では リテラル が使われる 使用 let basket:Basket = .pineapple switch basket { case .Pineapple: … case .Melon: … }

34.

まったく新しい列挙型 要点2 Raw 値を取得したとき… 1. 初めてインスタンスが生成される 2. 変換イニシャライザ が実行される 3. 列挙子が対応するリテラルが渡される Basket.Melon.rawValue MyClass(floatLiteral: 1.1) MyClass

35.

まったく新しい列挙型 要点3 Raw 値から列挙型を生成するとき… 1. Raw 値から インスタンスを生成 2. 列挙子を 順次 インスタンス化して比較 3. 戻り値は MyEnum? 型 4. 該当しない場合は nil を返す

36.

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

37.

まったく新しい列挙型 要点4 リテラルから列挙子を作るときも… 1. いったん 独自型に変換 される 2. 列挙子を 順次 インスタンス化して比較 MyEnum(rawValue: 1.0) MyEnum(rawValue: MyClass(floatLiteral: 1.1)) MyEnum?

38.

まったく新しい列挙型 つまり独自型の Raw 値は… 1. 使い出すとハイコスト その都度インスタンス化が行われる 2. 使わなければリテラルと同等 列挙子自体は独自型では管理されない ― 列挙子とインスタンスを関連付ける的な役割 ―

39.

まったく新しい列挙型 ? 3. 列挙子と合わせて値を持てる

40.

値付き列挙型 associated values ― 値を持てる列挙型 ―

41.

値付き列挙型 値付き列挙型 定義 Swift enum Basket { case Empty case Fruit(String) case Animal(String) } ― 列挙子にデータ型を添えて定義 ―

42.

値付き列挙型 値付き列挙型 列挙子を使う 宣言と代入 let basket1 = Basket.Fruit("りんご") let basket2 = Basket.Animal("ライオン") let basket3 = Basket.Empty ― 値付き列挙子には値を必ず添える ―

43.

値付き列挙型 値付き列挙型 値を加味した篩い分け 分岐 let basket:Basket switch basket { case .Fruit("みかん"): … case .Fruit("りんご"): … default: … } ― 列挙子と値での篩い分けが可能 ―

44.

値付き列挙型 値付き列挙型 大まかな篩い分け 分岐 let basket:Basket switch basket { case .Fruit: … case .Animal: … case .Empty: … } ― 列挙子だけでの篩い分けも可能 ―

45.

値付き列挙型 値付き列挙型 値を加味したり、しなかったり 分岐 let basket:Basket switch basket { case .Fruit("みかん"): … case .Fruit: … default: … } ― 自由に混在可能 ―

46.

値付き列挙型 値付き列挙型 値を取り出して利用する 分岐・値の取得 let basket:Basket switch basket { case let .Fruit(fruit): println("\(fruit)!") } ― 列挙子が一致したとき、その値を取り出す ―

47.

値付き列挙型 複数の値も付けられる

48.

値付き列挙型 値が複数付いた列挙型 定義 Swift enum Basket { case Empty case Fruit(String, Int) case Animal(String, Int, Bool) } ― タプル型で列挙子の値を定義 ―

49.

値付き列挙型 値が複数付いた列挙型 値を取り出して利用する 分岐と各値の判定・取得 let basket:Basket switch basket { case let .Fruit("りんご", price): println("\りんごは特売! \(price)円!") case let .Fruit(name, price): println("\(name)が\(price)円!") } ― 値を個別に取り出せる、どれか固定も可能 ―

50.

値付き列挙型 値が複数付いた列挙型 値を一括で取り出すことも可能 分岐と値の一括取得 let basket:Basket switch basket { case let .Fruit(value): println("\(value.0)が\(value.1)円!") } ― 値をタプル型で取り出せる ―

51.

値付き列挙型 値が複数付いた列挙型 名前付きタプルも利用可能 定義 enum Basket { case Empty case Fruit(name: String, price: Int) } 使用 switch basket { case let .Fruit(value): println("\(value.name)が\(value.price)円!") }

52.

ジェネリックな 値付き列挙型 ― 汎用的な値を持つ ―

53.
[beta]
ジェネリックな値付き列挙型

ジェネリックとは
Generics

汎用的に型を扱う仕組み
― 型に縛られないコードが書ける ―

ジェネリック関数

func makePair<T,U>(first:T, second:U)->(T,U) {
return (first, second)
}

54.

ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 定義 Swift enum Basket<Type> { case Empty case Fruit(Type) case Animal(String) } 上 G N 様 仕 ジェネリックを値に持つ列挙型で 複数の値付き列挙子を定義できない

55.

ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 定義 Swift enum Basket<Type> { case Empty case Nandemo(Type) } ― 列挙子が採る値の型を汎用化 ―

56.

ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 型に縛られない列挙型の使用 宣言と代入 let basket1:Basket<String> = Basket.Nandemo("りんご") let basket2:Basket<Int> = Basket.Nandemo(1000) 1. 渡した値に応じて列挙型が決まる 2. 宣言以降は値の型を変えられない 3. あくまでもコードの汎用化が目的

57.

ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 扱い方は通常と同じ 判定 switch basket { case let .Nandemo(value): … case .Empty: … } ― 値の型は宣言時に決めた型 ―

58.

ジェネリックな値付き列挙型 ところで…

59.

ジェネリックな値付き列挙型 ジェネリックな値付き列挙型 似たコンセプトの列挙型… この列挙型と、 enum Basket<Type> { case Empty case Nandemo(Type) } この列挙型と。 enum Optional<T> { case None case Some(T) }

60.

ジェネリックな値付き列挙型 どこかで見覚えのある列挙型 enum Optional<T> { } case None case Some(T)

61.

そして伝説へ…

62.

そして伝説へ… Optional<T> ― nil を持てる型 ―

63.

そして伝説へ… Optional<T> オプショナルとは? 1. 任意の値を格納できる型 2. 値の型は宣言時に決定 3. 値がない状態を “nil” で表現可能 4. Swift 言語の重要な機能

64.

そして伝説へ… Swift の Optional って String?

65.

シンタックスシュガー ― 簡単に書くための構文 ―

66.

シンタックスシュガー String? is Optional<String> ― 明示的な扱いが必要な nil 許容型 ―

67.

そして伝説へ… Optional<T> Swift で定義されている

68.

そして伝説へ… Optional<T> 定義から見る特徴 1. ジェネリックな値付き列挙型 2. ある値 .Some(T) と 何もない値 .None を採る 3. 引数名なしで値を採るイニシャライザ init(̲ some:T) 4. nil リテラルから変換可能 NilLiteralConvertible

69.

ジェネリックな値付き列挙型 シンタックスシュガー ということは

70.

シンタックスシュガー この2つは同じことを表現 嘘のような本当の話 シンタックスシュガー let str1:String? = "STRING" let str1:String? = nil 列挙型による実装 let str2:Optional<String> = .Some("STRING") let str2:Optional<String> = .None

71.

シンタックスシュガー 同じことなので混在も可能 表現は違っても値は同じ シンタックスシュガーと列挙型の混在 // 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>()

72.

シンタックスシュガー 実際の動きを知ると Optional が分かりやすくなる

73.

シンタックスシュガー 値を Optional でラップする Optional 型に値を設定 シンタックスシュガー // 型の最後に「?」を付ける let str:String? = "STRING" 列挙型による実装 // 列挙型 Optional.Some の値として指定する let str2 = Optional.Some("STRING") // Optional<T> のイニシャライザでも良い let str2 = Optional("STRING")

74.

シンタックスシュガー Optional に nil を代入する Optional 型に nil を設定 シンタックスシュガー // 型の最後に「?」を付けた変数に nil を代入する let str:String? = nil 列挙型による実装 // Optional<T>.None を代入する(要・型の明示) let str2 = Optional<String>.None // Optional<T> のイニシャライザでも良い let str2 = Optional<String>()

75.

シンタックスシュガー 値が nil かを判定する 値があるかないかを if で判定 シンタックスシュガー if str != nil { } else { } 列挙型による実装 switch str { case .Some: … case .None: … }

76.

シンタックスシュガー 値が nil かを判定する Optional Binding で値を取得 シンタックスシュガー if let value = str { } else { } 列挙型による実装 switch str { case let .Some(value): … case .None: … }

77.

シンタックスシュガー Optional から値を取り出す 強制アンラップ シンタックスシュガー let value = str! 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: abort() }

78.

シンタックスシュガー Optional から値を取り出す nil 結合演算子(右辺が nil 非許容のとき) シンタックスシュガー let value = str ?? "DEFAULT" 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: value = "DEFAULT" }

79.

シンタックスシュガー Optional から値を取り出す nil 結合演算子(右辺が nil 許容のとき) シンタックスシュガー let value = str ?? Optional("DEFAULT") 列挙型による実装 var value:String? switch str { case let .Some: value = str! case .None: value = Optional("DEFAULT") }

80.

シンタックスシュガー Optional から値を取り出す Optional Chaining で値を取得 シンタックスシュガー let value = str?.hashValue 列挙型による実装 var value:Int? switch str { case let .Some(v): value = v.hashValue case .None: value = nil }

81.

! も ? も ?? も バリエーション nil かどうかで「どうするか」 が少し違うだけ

82.

シンタックスシュガー シンタックスシュガーのおかげで Optional を簡単に扱える ― switch 文を省略できる ―

83.

ちなみに

84.

Optional<T> には func map<U>(f: (T)->U) -> U? 1. 値が nil なら nil を返す 2. 値が nil でなければ、引数で渡された クロージャの実行結果をラップして返す

85.
[beta]
シンタックスシュガー

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

86.

もうひとつ

87.

ImplicitlyUnwrappedOptional<T> ― Optional の姉妹品 ―

88.

シンタックスシュガー String! is ImplicitlyUnwrappedOptional<String> ― 暗黙的にアンラップされる nil 許容型 ―

89.

シンタックスシュガー ImplicitlyUnwrappedOptional 値のラップは Optional と同等 シンタックスシュガー // 型の最後に「!」を付ける let str:String! = "STRING" 列挙型による実装 // 列挙型 .Some の値として指定する let str2 = ImplicitlyUnwrappedOptional.Some("STRING")

90.

シンタックスシュガー ImplicitlyUnwrappedOptional nil の設定は Optional と同等 シンタックスシュガー // 型の最後に「!」を付けた変数に nil を代入する let str:String! = nil 列挙型による実装 // .None を代入する(要・型の明示) let str2 = ImplicitlyUnwrappedOptional<String>.None

91.

シンタックスシュガー ImplicitlyUnwrappedOptional 値の nil 判定は Optional と同等 シンタックスシュガー if str != nil { } else { } 列挙型による実装 switch str { case .Some: … case .None: … }

92.

シンタックスシュガー ImplicitlyUnwrappedOptional Optional Binding は存在しない ( if let v = str ) ― 暗黙アンラップが基本 ―

93.

シンタックスシュガー ImplicitlyUnwrappedOptional アンラップは型が明確なら暗黙的に実施 シンタックスシュガー let value = str! let value:String = str 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: abort() } // 明示アンラップ // 暗黙アンラップ

94.

シンタックスシュガー ImplicitlyUnwrappedOptional nil 結合演算子は Optional と同等(nil 非許容) シンタックスシュガー let value = str ?? "DEFAULT" 列挙型による実装 var value:String switch str { case let .Some(v): value = v case .None: value = "DEFAULT" }

95.
[beta]
シンタックスシュガー

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")
}

96.

シンタックスシュガー ImplicitlyUnwrappedOptional Optional Chaining は無くて暗黙アンラップ シンタックスシュガー //「?」はなく即時アンラップ(nil なら強制終了) let value = str.hashValue 列挙型による実装 var value:Int switch str { case let .Some(v): value = v.hashValue case .None: abort() }

97.

シンタックスシュガー ImplicitlyUnwrappedOptional も Optional と僅かに違うだけ 1. 明示的/暗黙的アンラップ str! 2. Optional Binding の有無 if let v = str 3. Optional Chaining の有無 str?.hashValue

98.

仕組みを意識すると Optional って意外とシンプル ― 列挙型のシンタックスシュガー ―

99.

以上 値付き enum 入門 列挙型の基礎から Swift での実用例までのお話でした

100.

『値付き enum 入門』 1. 普通な列挙型 case Pineapple, Melon, Empty 2. Raw 値を持てる列挙型 enum Basket : MyClass { 3. 列挙子ごとに値を持てる列挙型 case Fruit(String) 4. ジェネリックな値付き列挙型 enum Basket<T> { 5. Optional も列挙型 enum Optional<T> {