値付き enum 入門、そして伝説へ #yhios #cocoa kansai
TRANSCRIPT
EZ-NET 熊谷友宏 http://ez-net.jp/
2014.11.29 @ 横浜へなちょこiOS勉強会 #34.1
~ そして伝説へ ~値付き enum 入門
2014.12.06 @ 第59回 Cocoa 勉強会関西
自己紹介
EZ-NET 熊谷友宏 @es_kumagai
Xcode 5 徹底解説
IP Phone 音でダイヤル 音で再配達ゴッド
いつもの電卓 for iPhone
いつもの電卓 for iPad
音で再配達
列挙型
― いよいよ enum は究極形態へ ―
普通の列挙型
列挙型
列挙型定義
enum Basket {
case Empty
case Pineapple case Melon }
Swift
普通の列挙型
1. 限られた候補の中から値を採るデータ型 2. 意味で値をひとまとめにできる 3. 値を言葉で扱えるのでコードが明瞭に
列挙型使い方
let value:Basket
switch value {
case .Pineapple: …
case .Melon: …
case .Empty: … }
Swift
普通の列挙型
やや新しい列挙型
列挙型
列挙型Raw 値の割り当て
やや新しい列挙型
1. 列挙子に内部的な値を付与 2. Objective-C では必ず Raw 値が設定される 3. Swift で Raw 値がない列挙子は具体値なし
enum Basket : Int {
case Empty = 0
case Pineapple = 1 case Melon = 2 }
定義
列挙型列挙子から Raw 値を取得
let raw = Basket.Melon.rawValueRaw 値を取得
let value = Basket(rawValue: raw)!列挙子を生成
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. 整数値リテラル … 0IntegerLiteralConvertible
2. 浮動小数点数値リテラル … 0.0FloatLiteralConvertible
3. 真偽値リテラル … trueBooleanLiteralConvertible
4. 文字列リテラル … "STRING"StringLiteralConvertible
[OK]
[OK]
[NG]
[OK]
まったく新しい列挙型
これもリテラル
1. nil リテラル … nilNilLiteralConvertible
2. 配列リテラル … [ value, ... ]ArrayLiteralConvertible
3. 辞書リテラル … [ key : value, ... ]DictionaryLiteralConvertible
[NG]
[NG]
[NG]
まったく新しい列挙型
使えるものに注目すれば …
1. 整数値リテラル 2. 浮動小数点数値リテラル 3. 文字列リテラル
まったく新しい列挙型
列挙子の値が…
文字列 でも良い
まったく新しい列挙型
Raw 値を文字列型にする…
enum Basket : String {
case Empty = ""
case Pineapple = "Pineapple" case Melon = "Melon" }
定義
まったく新しい列挙型
1. 内部的な値を文字列で付与 2. 列挙子と文字列値とが関連付けられる
Raw 値として文字列を取り出せる
let string:String = Basket.Melon.rawValueRaw 値の取得
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列挙子を使う分には…
let basket:Basket = .pineapple
switch basket {
case .Pineapple: … case .Melon: … }
使用
1. 独自型は 全く使われない 2. 列挙子の操作では リテラル が使われる
まったく新しい列挙型
要点2Raw 値を取得したとき…
1. 初めてインスタンスが生成される 2. 変換イニシャライザ が実行される 3. 列挙子が対応するリテラルが渡される
Basket.Melon.rawValue
MyClass(floatLiteral: 1.1)
MyClass
まったく新しい列挙型
要点3Raw 値から列挙型を生成するとき…
1. Raw 値から インスタンスを生成 2. 列挙子を 順次 インスタンス化して比較 3. 戻り値は MyEnum? 型 4. 該当しない場合は nil を返す
まったく新しい列挙型
MyClass(floatLiteral: 0.0) == obj
let obj = MyClass(floatLiteral: 1.0)
MyClass(floatLiteral: 1.1) == obj
MyEnum?
MyClass(floatLiteral: 1.0) == obj
case .Empty
case .Pineapple
case .Melon
MyEnum(rawValue: obj)
要点4リテラルから列挙子を作るときも…
1. いったん 独自型に変換 される 2. 列挙子を 順次 インスタンス化して比較
MyEnum(rawValue: 1.0)
MyEnum(rawValue: MyClass(floatLiteral: 1.1))
MyEnum?
まったく新しい列挙型
つまり独自型の Raw 値は…
1. 使い出すとハイコストその都度インスタンス化が行われる
2. 使わなければリテラルと同等列挙子自体は独自型では管理されない
― 列挙子とインスタンスを関連付ける的な役割 ―
まったく新しい列挙型
?3. 列挙子と合わせて値を持てる
まったく新しい列挙型
値付き列挙型
― 値を持てる列挙型 ―
associated values
値付き列挙型定義
enum Basket {
case Empty
case Fruit(String) case Animal(String) }
Swift
― 列挙子にデータ型を添えて定義 ―
値付き列挙型
値付き列挙型列挙子を使う
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)!") }
分岐・値の取得
― 列挙子が一致したとき、その値を取り出す ―
値付き列挙型
複数の値も付けられる
値付き列挙型
値が複数付いた列挙型定義
enum Basket {
case Empty
case Fruit(String, Int) case Animal(String, Int, Bool) }
Swift
― タプル型で列挙子の値を定義 ―
値付き列挙型
値が複数付いた列挙型値を取り出して利用する
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)円!") }
分岐と値の一括取得
― 値をタプル型で取り出せる ―
値付き列挙型
値が複数付いた列挙型名前付きタプルも利用可能
switch basket {
case let .Fruit(value): println("\(value.name)が\(value.price)円!") }
使用
enum Basket {
case Empty
case Fruit(name: String, price: Int) }
定義
値付き列挙型
ジェネリックな 値付き列挙型
― 汎用的な値を持つ ―
ジェネリックとはGenerics
汎用的に型を扱う仕組み― 型に縛られないコードが書ける ―
func makePair<T,U>(first:T, second:U)->(T,U) {
return (first, second) }
ジェネリック関数
ジェネリックな値付き列挙型
ジェネリックな値付き列挙型定義
enum Basket<Type> {
case Empty
case Fruit(Type) case Animal(String) }
Swift
ジェネリックを値に持つ列挙型で 複数の値付き列挙子を定義できない
仕様上 N
G
ジェネリックな値付き列挙型
ジェネリックな値付き列挙型定義
enum Basket<Type> {
case Empty
case Nandemo(Type) }
Swift
― 列挙子が採る値の型を汎用化 ―
ジェネリックな値付き列挙型
1. 渡した値に応じて列挙型が決まる 2. 宣言以降は値の型を変えられない 3. あくまでもコードの汎用化が目的
ジェネリックな値付き列挙型型に縛られない列挙型の使用
let basket1:Basket<String> = Basket.Nandemo("りんご")
let basket2:Basket<Int> = Basket.Nandemo(1000)
宣言と代入
ジェネリックな値付き列挙型
ジェネリックな値付き列挙型扱い方は通常と同じ
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?
そして伝説へ…
シンタックスシュガー― 簡単に書くための構文 ―
is
String?
Optional<String>
シンタックスシュガー
― 明示的な扱いが必要な nil 許容型 ―
Optional<T>Swift で定義されている
そして伝説へ…
Optional<T>定義から見る特徴
1. ジェネリックな値付き列挙型 2. ある値 .Some(T) と何もない値 .None を採る
3. 引数名なしで値を採るイニシャライザinit(_ some:T)
4. nil リテラルから変換可能NilLiteralConvertible
そして伝説へ…
シンタックスシュガー ということは
ジェネリックな値付き列挙型
let str1:String? = "STRING" let str1:String? = nil
let str2:Optional<String> = .Some("STRING") let str2:Optional<String> = .None
嘘のような本当の話この2つは同じことを表現
シンタックスシュガー
シンタックスシュガー
列挙型による実装
// 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 が分かりやすくなる
シンタックスシュガー
// 型の最後に「?」を付ける let str:String? = "STRING"
// 列挙型 Optional.Some の値として指定する let str2 = Optional.Some("STRING")
// Optional<T> のイニシャライザでも良い let str2 = Optional("STRING")
値を Optional でラップするOptional 型に値を設定
シンタックスシュガー
シンタックスシュガー
列挙型による実装
// 型の最後に「?」を付けた変数に nil を代入する let str:String? = nil
// Optional<T>.None を代入する(要・型の明示) let str2 = Optional<String>.None
// Optional<T> のイニシャライザでも良い let str2 = Optional<String>()
Optional に nil を代入するOptional 型に nil を設定
シンタックスシュガー
シンタックスシュガー
列挙型による実装
if str != nil {
} else {
}
switch str { case .Some: … case .None: … }
値が nil かを判定する値があるかないかを if で判定
シンタックスシュガー
シンタックスシュガー
列挙型による実装
if let value = str {
} else {
}
switch str { case let .Some(value): … case .None: … }
値が nil かを判定するOptional Binding で値を取得
シンタックスシュガー
シンタックスシュガー
列挙型による実装
let value = str!
var value:String
switch str {
case let .Some(v): value = v
case .None: abort() }
Optional から値を取り出す強制アンラップ
シンタックスシュガー
列挙型による実装
シンタックスシュガー
let value = str ?? "DEFAULT"
Optional から値を取り出すnil 結合演算子(右辺が nil 非許容のとき)
シンタックスシュガー
var value:String
switch str {
case let .Some(v): value = v
case .None: value = "DEFAULT" }
列挙型による実装
シンタックスシュガー
let value = str ?? Optional("DEFAULT")
Optional から値を取り出すnil 結合演算子(右辺が nil 許容のとき)
シンタックスシュガー
var value:String?
switch str {
case let .Some: value = str!
case .None: value = Optional("DEFAULT") }
列挙型による実装
シンタックスシュガー
let value = str?.hashValue
Optional から値を取り出すOptional Chaining で値を取得
シンタックスシュガー
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 でなければ、引数で渡されたクロージャの実行結果をラップして返す
let value = str?.hashValue
Optional から値を取り出すOptional<T>.map<U> メソッドで実行
シンタックスシュガー
// いちばん短い書き方 let value = str.map { $0.hashValue }
// 省略しない書き方 let value = str.map({ (value:String) -> Int in
return value.hashValue })
Optional 型による実装
シンタックスシュガー
もうひとつ
ImplicitlyUnwrappedOptional<T>
― Optional の姉妹品 ―
isString!
ImplicitlyUnwrappedOptional<String>
シンタックスシュガー
― 暗黙的にアンラップされる nil 許容型 ―
// 型の最後に「!」を付ける let str:String! = "STRING"
シンタックスシュガー
// 列挙型 .Some の値として指定する let str2 = ImplicitlyUnwrappedOptional.Some("STRING")
列挙型による実装
ImplicitlyUnwrappedOptional値のラップは Optional と同等
シンタックスシュガー
// 型の最後に「!」を付けた変数に nil を代入する let str:String! = nil
// .None を代入する(要・型の明示) let str2 = ImplicitlyUnwrappedOptional<String>.None
シンタックスシュガー
ImplicitlyUnwrappedOptionalnil の設定は Optional と同等
シンタックスシュガー
列挙型による実装
if str != nil {
} else {
}
switch str { case .Some: … case .None: … }
シンタックスシュガー
ImplicitlyUnwrappedOptional値の nil 判定は Optional と同等
シンタックスシュガー
列挙型による実装
Optional Binding は存在しない
シンタックスシュガー
ImplicitlyUnwrappedOptional
― 暗黙アンラップが基本 ―
( if let v = str )
let value = str! // 明示アンラップ let value:String = str // 暗黙アンラップ
var value:String
switch str {
case let .Some(v): value = v
case .None: abort() }
列挙型による実装
シンタックスシュガー
アンラップは型が明確なら暗黙的に実施ImplicitlyUnwrappedOptional
シンタックスシュガー
let value = str ?? "DEFAULT"
nil 結合演算子は Optional と同等(nil 非許容)
シンタックスシュガー
var value:String
switch str {
case let .Some(v): value = v
case .None: value = "DEFAULT" }
ImplicitlyUnwrappedOptional
列挙型による実装
シンタックスシュガー
// 結果は Optional<T> で得られる let value = str ?? Optional("DEFAULT")
シンタックスシュガー
var value:String?
switch str {
case let .Some: value = str!
case .None: value = Optional("DEFAULT") }
nil 結合演算子は Optional と同等(nil 許容)ImplicitlyUnwrappedOptional
列挙型による実装
シンタックスシュガー
//「?」はなく即時アンラップ(nil なら強制終了) let value = str.hashValue
シンタックスシュガー
var value:Int
switch str {
case let .Some(v): value = v.hashValue
case .None: abort() }
Optional Chaining は無くて暗黙アンラップImplicitlyUnwrappedOptional
列挙型による実装
シンタックスシュガー
ImplicitlyUnwrappedOptional も Optional と僅かに違うだけ
シンタックスシュガー
1. 明示的/暗黙的アンラップ 2. Optional Binding の有無 3. Optional Chaining の有無
str!
if let v = str
str?.hashValue
仕組みを意識すると Optional って意外とシンプル
― 列挙型のシンタックスシュガー ―
列挙型の基礎から Swift での実用例までのお話でした
値付き enum 入門
以上
『値付き enum 入門』
1. 普通な列挙型case Pineapple, Melon, Empty
2. Raw 値を持てる列挙型enum Basket : MyClass {
3. 列挙子ごとに値を持てる列挙型case Fruit(String)
4. ジェネリックな値付き列挙型enum Basket<T> {
5. Optional も列挙型enum Optional<T> {