20141128 iOSチーム勉強会 My Sweet Swift

Post on 03-Aug-2015

750 views 2 download

Transcript of 20141128 iOSチーム勉強会 My Sweet Swift

My Sweet Swift

Agenda

• Generics

• Enumeration

• Optional

• Appendix

Agenda

• Generics

• Enumeration

• Optional

• Appendix

}準備本題

Generics

Generics• 型をパラメータにとる関数・データ構造

• 型情報を保ったまま、複数の型に同時に対応できる

• とは?

例: 入力をそのまま返す関数

func identity(input: Int) -> Int { return input } !let number = identity(42) // Int let string = identity("Lorem ipusm") // error

例: 入力をそのまま返す関数

func identity(input: Any) -> Any { return input } !let number = identity(42) // Any let string = identity("Lorem ipsum") // Any !number * 2 // error string + string // error

例: 入力をそのまま返す関数

func identity<T>(input: T) -> T { return input } !let number = identity(42) // Int let string = identity("Lorem ipsum") // String !number * 2 string + string

例: map

x

"\(x)"

ffunc f(x: Int) -> String { return "\(x)" }

例: map

x [1, 2, 3]

"\(x)"

f

例: map

x [1, 2, 3]

["1", "2", "3"]"\(x)"

f

例: map

x [1, 2, 3]

["1", "2", "3"]"\(x)"

f

map([Int], Int -> String) -> [String]

例: map

func f(x: Int) -> String { return "\(x)" } !func map(xs: [Int], f: Int -> String) -> [String] { var ys = [String]() for x in xs { ys.append(f(x)) } return ys } !map([1, 2, 3], f) // ["1", "2", “3"] map([1, 2, 3], { "\($0)" }) // ["1", "2", "3"]

例: map

Int [Int]

[String]String

f

map([Int], Int -> String) -> [String]

例: map

T [T]

[U]U

T -> U

map([T], T -> U) -> [U]

例: map

func map<T, U>(xs: [T], f: T -> U) -> [U] { var ys = [U]() for x in xs { ys.append(f(x)) } return ys } !// [Int] -> [String] map([1, 2, 3], { "\($0)" }) // ["1", "2", "3"] // [Int] -> [Double] map([1, 2, 3], { $0 * 2.0 }) // [2.0, 4.0, 6.0] // [Double] -> [CGRect] map([1, 2, 3], { CGRect(x: $0, y: $0, width: 100.0, height: 100.0) })

クラスの例: Stack

class Stack<T> { private var array = [T]() func push(item: T) { array.append(item) } func pop() -> T { return array.removeLast() } } !let stack = Stack<Int>() stack.push(1) stack.push(2) stack.push(3) stack.pop() // 3 stack.pop() // 2 stack.pop() // 1

Enumerations

Enums

enum BloodType { case A, B, O, AB } !struct Person { var bloodType: BloodType } !var p = Person(bloodType: BloodType.A) var q = Person(bloodType: .A) q.bloodType = .AB

Enums

• ObjCのEnumと違って、「値」を持たない

• ex. 東西南北に整数を割り当てる意味?

• 従来通りに値を持たせることもできる

• 文字列もOK

…with associated values

/// ダイアログのUI要素を指定するenum。 enum DialogItem { case TextButton(title: String, identifier: String) case GrayButton(title: String, identifier: String) case Text(String) case Title(String) case Margin(CGFloat) case Group([DialogItem]) } !let item1 = DialogItem.Margin(20) let item2 = DialogItem.Text("String")

Associated Values

• enumの各状態に値を紐づけることができる

• 型が違っていてもいい

• 単なる列挙というよりは、「複数の型のどれか」を表す型になる

Switchで値を展開

switch item { case let .Title(title): println("Title: \(title)") case let .Text(text): println("Text: \(text)") case let .TextButton(title: title, identifier: identifier): /// case let .GrayButton(title: title, identifier: identifier): /// case let .Group(subitems): /// default: break }

例: DialogViewController

let items: [DialogViewController.DialogItem] = [ .Title("ダイアログ"), .Text("表示要素を配列で渡すだけで、自在にダイアログを作ることができます。"), .TextButton(title: "ボタン", identifier: "button1"), .Margin(60), .Group([ .Group([.Text("二段組み"), .Text("にも")]), .Group([.Text("対応"), .Text("(たぶん)")]) ]), .Group([ .TextButton(title: "ボタン", identifier: "button2"), .GrayButton(title: "ボタン", identifier: "button3"), .TextButton(title: "ボタン", identifier: "button4") ]) ] let dialog = DialogViewController(items: items, callback: nil) presentViewController(dialog, animated: true, completion: nil)

簡単な例: Failable

• 「失敗するかもしれない」結果を記述する

• 失敗すればFailed、成功すればSucceeded

• 成功した場合は結果をAssociated Valueに持つ

例: Failable<T>

enum Failable<T> { case Succeeded(T) // 成功したときの値 case Failed // 失敗フラグ init(_ value: T) { self = .Succeeded(value) } }

例: Failable<T>

// 2で割れたら割るけど割れなかったらあきらめる(やる気のない)関数 func failableHalve(x: Int) -> Failable<Int> { if x % 2 == 0 { return .Succeeded(x / 2) } else { return .Failed } } !switch failableHalve(12) { case let .Succeeded(r): println("Answer: \(r)") default: println("Odd Number") }

それって

// 2で割れたら割るけど割れなかったらあきらめる(やる気のない)関数 func failableHalve(x: Int) -> Optional<Int> { if x % 2 == 0 { return .Some(x / 2) } else { return .None } } !switch failableHalve(12) { case let .Some(r): println("Answer: \(r)") default: println("Odd Number") }

Optionalなのでは?

// 2で割れたら割るけど割れなかったらあきらめる(やる気のない)関数 func failableHalve(x: Int) -> Int? { if x % 2 == 0 { return x / 2 } else { return nil } } !!if let r = failableHalve(12) { println("Answer: \(r)") } else { println("Odd Number") }

Optional

太古よりnullは人々を苦しめてきた

Optional以前• オブジェクトとはポインタである

• 特別なアドレス(多くの場合0)をNULLと呼び、そこを指すポインタを「無効なオブジェクト」として扱う

問題点• NULLを無効な値だと知っているのは実行中のプログラムとプログラマだけ

• コンパイラには他のポインタ値と区別できない

• →実行時に落ちる

• 予期せぬNULLが紛れ込まないように「気をつける」「確認する」ことしかできなかった

Optionalのいいところ• 「無効な値」という概念を型にしたことで、意味論ではなく構文論として扱える

• 無効な値を扱う操作が「書けない」文法

Optional<T>enum Optional<T> : Reflectable, NilLiteralConvertible { case None case Some(T) ! /// Construct a `nil` instance. init() ! /// Construct a non-\ `nil` instance that stores `some`. init(_ some: T) ! /// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`. func map<U>(f: (T) -> U) -> U? ! /// Returns a mirror that reflects `self`. func getMirror() -> MirrorType ! /// Create an instance initialized with `nil`. init(nilLiteral: ()) }

Optional<T>

• 実体はGeneric Enum

• 実はちょっと嘘で、特別扱いを受けている

• cf. http://qiita.com/koher/items/5dd6ce7427e7ecff4249

• (Failable<T>と同じように)箱です

Optional as 箱• 「Tかもしれない」ではなく、「Tの入る箱」

• 値に触るためには箱を開ける必要がある

• binding/coalescing/unwrapping

• 箱を開けずに操作する方法もある

• mapping/chaining

Optional Binding

var optionalInt: Int? !if let int = optionalInt { // int: Int println("optionalInt was \(int)") } else { println("optionalInt was nil") } !var array = [1, 2, 3, 4] while let item = array.last { // item: Int println("\(item)") array.removeLast() }

箱を開ける: Optional Binding

• 値があればtrueに評価されつつ中身を変数に束縛(bind)してくれる

• 一度に一個しかbindできないのは多少不便

Optional Coalescing

optionalInt ?? 3 !// ↓と等価: optionalInt != nil ? optionalInt! : 3 !// あるいは: func coalesce<T>(lhs: T?, rhs: @autoclosure () -> T) -> T { if let lhs = lhs { return lhs } else { return rhs() } } coalesce(optionalInt, 3)

箱を開ける: Optional Coalescing

• 開けられたら開ける

• 開けられなかったら開けない

• かわりにデフォルト値を返す

無理矢理開ける: Forced Unwrapping

• 値があればそれが返るが、なければ落ちる

• 落ちる可能性があるので使うべきではない

• 安全が保証できるなら構わないが……

optionalInt!

箱から出さずに進める方法• Optional mapping

• Optional chaining

Optional Mapping

/// 3倍する関数 func triple(x: Int) -> Int { return x * 3 } !optionalInt = 2 // 以下はすべて.Some 6 optionalInt.map(triple) map(optionalInt, triple) optionalInt.map({ $0 * 3 })

箱を開けない: Optional Mapping

• 箱の中から別の箱の中へ

• 値があればそれを移して別の箱(Optional)を作る

• nilならばnilを返す

Optional Mapping

T

U

T -> U

map(T?, T -> U) -> U?

U?

T?

Optional Chaining

class A { var b: B? } !class B { var c: C? } !class C { var i: Int = 1 } var a: A? = A() // a.b.c.iにアクセスしたい

Optional Chaining

var a: A? !if let a = a { if let b = a.b { if let c = b.c { println("We finally find \(c.i)!") } } }

Optional Chaining

var a: A? !if let i = a?.b?.c?.i { println("Optional chaining makes \(i)t easy.") }

箱を開けない: Optional Chaining

• 失敗するかもしれない呼び出しを連鎖できる

• 失敗すればその先には進まずnil

Optional Chaining

a?.b?.c?.iA?

aはA?型の変数

Optional Chaining

a.bA?

A?型にbというメンバはない

×

Optional Chaining

a?.b?.c?.iA?

a?.bという形にする

Optional Chaining

a?.b?.c?.iA()

or

nil

aはnilかSomeのどちらか

Optional Chaining

a?.b?.c?.iA()

or

nil

B?

Someならばbに触れる

Optional Chaining

a?.b?.c?.iA()

or

nil nil

B?

nilならそれで終わり

Optional Chaining

a?.b?.c?.iA()

or

nil nil

B?

bはB?型の変数

Optional Chaining

a?.b?.c?.iA()

or

nil nil

B()

or

nil

C?

同じように分岐

Optional Chaining

a?.b?.c?.iA()

or

nil nil

B()

or

nil

C()

nil

or

7

以下同文

書き直してみる

extension Optional { func chain<U>(f: T -> U?) -> U? { if let s = self { return f(s) } else { return nil } } } !if let i = a.chain({ $0.b }).chain({ $0.c }).chain({ $0.i }) { println("\(i)") } !// (正確には↓だが説明は省略(結果は等しくなる)) // let k = a.chain({ $0.b.chain({ $0.c.chain({ $0.i }) }) })

なぜ書き直したの?• メンバ関数以外にも使えるようにしたい

• これが今日の主役です

Failable Halve

Int Int?// 2で割れたら割るけど割れなかったらあきらめる(やる気のない)関数 func failableHalve(x: Int) -> Int? { if x % 2 == 0 { return x / 2 } else { return nil } } !if let a = failableHalve(x) { println("Halve(\(x)) = \(a)") } else { println("Failed") }

Failable Halve Again

Int Int?if let a = failableHalve(x) { if let b = failableHalve(a) { println("Halve^2(\(x)) = \(b)") } else { println("Failed") } } else { println("Failed") }

Int Int?

Failable Halve The 3rd

Int Int?if let a = failableHalve(x) { if let b = failableHalve(a) { if let c = failableHalve(b) { println("Halve^3(\(x)) = \(c)") } else { println("Failed") } } else { println("Failed") } } else { println("Failed") }

Int Int? Int Int?

Failable Halve Chaining

Int Int?if let a = failableHalve(x).chain(failableHalve).chain(failableHalve) { println("Halve^3(\(x)) = \(a)") } else { println("Failed") }

Int? Int?

Failable Halve Chaining

Int Int?24 failableHalve(24) // Some 12 failableHalve(24).chain(failableHalve) // Some 6 failableHalve(24).chain(failableHalve).chain(failableHalve) // Some 3 failableHalve(24).chain(failableHalve).chain(failableHalve).chain(failableHalve) // nil !36 failableHalve(36) // Some 18 failableHalve(36).chain(failableHalve) // Some 9 failableHalve(36).chain(failableHalve).chain(failableHalve) // nil failableHalve(36).chain(failableHalve).chain(failableHalve).chain(failableHalve) // nil

Int? Int?

Optional Chaining

t: T

T

Optional Chaining

f(t): U?

T U?f

Optional Chaining

T U?fU V?g

f(t): U?

Optional Chaining

T U?

f(t).chain(g): V?

f V?g

Optional Chaining

T U?

f(t).chain(g): V?

f V?g

V h W?

Optional Chaining

T U?

f(t).chain(g).chain(h): W?

f V?g W?h

Appendix

Array Mapping

T [T]

[U]U

T -> U

map([T], T -> U) -> [U]

Optional Mapping

T

U

T -> U

map( T?, T -> U) -> U?

U?

T?

Optional<T>とArray<T>

• T型の値が入った箱

• mapを使えば箱の中身だけを写すことができる

Optional as 状況• ところで、Optionalは「nilかもしれない」という状況(context)を表現していた

• Arrayはどんな状況を表現している?

• →「値が確定していない」

Array as 状況

Kyoto

目がさめると京都にいた

Kyoto

(0, 0)

歩く

Kyoto

(0, 1)

(0,-1)

(1, 0)(-1,0)

歩く

/// 座標 typealias Position = (Int, Int) !/// 東西南北のどこかへ歩きます func walk(from: Position) -> [Position] { let (x, y) = from return [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)] } !walk((0, 0)) // [(-1, 0), (1, 0), (0, -1), (0, 1)]

さらに歩く

Kyoto

さらに歩く

Kyoto

Array Chaining

T [U]U

V [W][V]

Array Chaining

extension Array { func chain<U>(f: Element -> [U]) -> [U] { var ys = [U]() for x in self { ys += f(x) } return ys } } !walk((0, 0)).chain(walk).chain(walk)

もっと状況を!• 「値が非同期で返ってくる」も状況

BFTask

doHeavyJobAsync1().continueWithBlock { (task: BFTask!) -> BFTask in let result = task.result as NSString self.resultLabel1.text = result self.resultLabel2.text = "処理中..." return self.doHeavyJobAsync2(result) }.continueWithBlock { (task: BFTask!) -> BFTask in let result = task.result as NSString self.resultLabel2.text = result self.resultLabel3.text = "処理中..." return self.doHeavyJobAsync3(result) }.continueWithBlock { (task: BFTask!) -> AnyObject! in let result = task.result as NSString self.resultLabel3.text = result return nil }

BFTask

private func doHeavyJobAsync2(prevResult: String) -> BFTask { var completionSource = BFTaskCompletionSource() // 5秒待ちの処理 // 実用的には、AFNetworkingのcompletionブロック等でsetResultするイメージ Util.delay(5, { completionSource.setResult(prevResult + "オワタ") }) return completionSource.task }

Task<T>

class Task<T: AnyObject> { private let task: BFTask ! init(task: BFTask) { self.task = task } var result: T? { return task.result as? T } func chain<U: AnyObject>(f: (T -> Task<U>)) -> Task<U> { let newTask = task.continueWithBlock { f($0.result as T).task } return Task<U>(task: newTask) } }

Taskを返す関数private func doHeavyJobAsync2(prevResult: NSString) -> Task<NSString> { let source = Source<NSString>() // 5秒待ちの処理 // 実用的には、AFNetworkingのcompletionブロック等でsetResultするイメージ delay(5, { source.setResult(prevResult + "オワタ" as NSString) }) return source.task }

Task Chaining

T UU

V WV

非同期

非同期非同期

Task Chaining

func heavyTask1(Void) -> Task<NSString> { return delayedTask(5) { NSLog("First Task (no params)") return "123" as NSString } } !func heavyTask2(string: NSString) -> Task<NSNumber> { return delayedTask(5) { NSLog("Second Task (param: \(string))") return NSNumber(integer: (string as String).toInt() ?? 0) } } !func heavyTask3(number: NSNumber) -> Task<NSNumber> { return delayedTask(5) { NSLog("Third Task (param: \(number))") return NSNumber(integer: number.integerValue * 2) } }

Task Chaining

NSLog("Start") heavyTask1().chain(heavyTask2).chain(heavyTask3) NSLog("End")

2014-11-27 21:15:24.659 MySweetSwift[18362:314049] Start 2014-11-27 21:15:24.663 MySweetSwift[18362:314049] End 2014-11-27 21:15:29.663 MySweetSwift[18362:314049] First Task (no params) 2014-11-27 21:15:35.136 MySweetSwift[18362:314049] Second Task (param: 123) 2014-11-27 21:15:40.636 MySweetSwift[18362:314049] Third Task (param: 123)

まとめ• Optionalはかわいい!

• ArrayとOptionalは似ている

• すなわち、箱であり、状況である

• 非同期処理も同じように見ることができる

• Optional Chainingのような書きかたができて、それはBFTaskに似ている

参考文献

以上