続・ゲンバのSwift

113
Developer Day 続・ゲンバのSwift 1 D-1 安達 勇一 クラスメソッド株式会社 Ⓒ Classmethod, Inc. 2015年03月29日 #cmdevio2015

Transcript of 続・ゲンバのSwift

Developer Day

続・ゲンバのSwift

1

D-1

安達 勇一 クラスメソッド株式会社

Ⓒ Classmethod, Inc.

2015年03月29日

#cmdevio2015

2

安達 勇一• 2013/12 入社

• TwitterID:   @UsrNameu1

• Github:    https://github.com/UsrNameu1

• Blog:    http://dev.classmethod.jp/author/yad

•        で連載やってます

Topics for today

• Enum

• Collection API

• BrightFutures

3Ⓒ Classmethod, Inc.

Topics for today

• Enum

• Collection API

• BrightFutures

4Ⓒ Classmethod, Inc.

Enum

• 離散値とEnum

• リソースとEnum

• エラーとEnum

5Ⓒ Classmethod, Inc.

離散値とEnum

• 決まった数値のみ許容するAPIのEndpoint :

6Ⓒ Classmethod, Inc.

POST api/v1/user[ { "name" : "Tarou", "gender" : 1 }, { "name" : "Yoko", "gender" : 2 }]

離散値とEnum

• 決まった数値のみ許容するAPIのEndpoint :

7Ⓒ Classmethod, Inc.

POST api/v1/user[ { "name" : "Tarou", "gender" : 1 }, { "name" : "Yoko", "gender" : 2 }]

決まった数値以外は 400 Bad Request

離散値とEnum

• 対応するモデル

8Ⓒ Classmethod, Inc.

struct User { let name: String let gender: Int}

離散値とEnum

• 対応するモデル

9Ⓒ Classmethod, Inc.

struct User { let name: String let gender: Int}

違う、そうじゃない!

struct User { let name: String let gender: Gender}

enum Gender: Int { case Male = 1 case Female = 2}

離散値とEnum

• 限られた値しかとらない数値はEnumにすることで…

→APIエラーを防げる

10Ⓒ Classmethod, Inc.

POST api/v1/user[ { "name" : "Tarou", "gender" : 1 }, { "name" : "Yoko", "gender" : 2 }]

決まった数値以外は 400 Bad Request

離散値とEnum

• 限られた値しかとらない数値はEnumにすることで…

11Ⓒ Classmethod, Inc.

switch user.gender {case 1 : println(“male”)case 2 : println(“female”)default : assert(false, “error”)}

離散値とEnum

• 限られた値しかとらない数値はEnumにすることで…

→網羅性チェックによりassertionを書く手間を省ける

12Ⓒ Classmethod, Inc.

switch user.gender {case .Male : println(“male”)case .Female : println(“female”)}

13Ⓒ Classmethod, Inc.

離散値はEnumにして IOを型として束縛

Enum

• 離散値とEnum

• リソースとEnum

• エラーとEnum

14Ⓒ Classmethod, Inc.

リソースとEnum

• Enumで宣言された値に対する

• NSLocalizedString • UIImage

→Objective-Cではコンバータやカテゴリ拡張を  別に実装するなどで対処。

15Ⓒ Classmethod, Inc.

リソースとEnum

• Enumで宣言された値に対する

• NSLocalizedString • UIImage

→SwiftではEnumに直接書けるようになりました!

16Ⓒ Classmethod, Inc.

リソースとEnum

• NSLocalizedStringを性別に対して取得

17Ⓒ Classmethod, Inc.

enum Gender: Int { case Male = 1 case Female = 2}

extension Gender { var localizedString: String { switch self { case .Male: return NSLocalizedString(“male”, comment: “”) case .Female: return NSLocalizedString(“female”, comment: “”) } }}

リソースとEnum

• UIImageを性別に対して取得

18Ⓒ Classmethod, Inc.

enum Gender: Int { case Male = 1 case Female = 2}

extension Gender { var thumbImage: UIImage { switch self { case .Male: return UIImage(named: “male”) case .Female: return UIImage(named: “female”) } }}

リソースとEnum

• Before

• After

19Ⓒ Classmethod, Inc.

cell.thumbImage = [UIImage thumbImageWithGender: gender];cell.title = [NSString localizedStringWithGender: gender];

cell.thumbImage = gender.thumbImagecell.title = gender.localizedString

20Ⓒ Classmethod, Inc.

Enumに紐付くリソースは 自身で管理・記述を簡潔化

Enum

• 離散値とEnum

• リソースとEnum

• エラーとEnum

21Ⓒ Classmethod, Inc.

エラーとEnum• Objective-Cでの典型的なエラーハンドリング (ライトバック渡し)

22Ⓒ Classmethod, Inc.

NSError *error = nil;NSData *aData = [NSData dataWithContentsOfURL:pathURL options:NSDataReadingUncached error:&error];if (!error) { // エラーがない時の処理} else { // エラーがある時の処理}

エラーとEnum

• エラーハンドリングについてはほぼ定形の処理

• エラーが無い時に続けて簡単に処理を書きたい…

• Optionalでは何のエラーかわからない…

23Ⓒ Classmethod, Inc.

エラーとEnum

24Ⓒ Classmethod, Inc.

• エラーハンドリングについてはほぼ定形の処理

• エラーが無い時に続けて簡単に処理を書きたい…

• Optionalでは何のエラーかわからない…

そのお悩み

Result<T>が

解決します!!!

エラーとEnum

• Result<T>

25Ⓒ Classmethod, Inc.

public enum Result<T> { case Success(Box<T>) case Failure(NSError)

public init(_ value: T) { self = .Success(Box(value)) } }

public final class Box<T> { public let value: T public init(_ value: T) { self.value = value } }

エラーの可能性 を持った値

コンパイラによるエラー 回避の為のBoxクラス

エラーとEnum• Before

• After

26Ⓒ Classmethod, Inc.

- (NSData *)dataWithURL:(NSURL *)filePathURL error:(NSError *)error { // エラーがあるときにはerrorにポインタを渡し // エラーがない時にnilでないNSData*を返す。 }

func dataWithURL(url: NSURL) -> Result<NSData> { // エラーがあるときもない時もResult型を返す。 }

エラーとEnum• map in Optional<T>

27Ⓒ Classmethod, Inc.

// Noneの可能性をもった値を let someInt = “127”.toInt()

// Noneの可能性を保ったまま中身だけ変更 someInt.map { val in val + 1 }

エラーとEnum• map in Optional<T>

28Ⓒ Classmethod, Inc.

Some

None

Optional<T> Optional<U>map(f: T -> U)

T

None

U

None

f

エラーとEnum• map in Result<T>

29Ⓒ Classmethod, Inc.

extension Result { func map<U>(f: T -> U) -> Result<U> { switch self { case .Success(let box) : return .Success(Box(f(box.value))) case .Failure(let err) : return .Failure(err) } } }

エラーの可能性を保って中の値をマッピングする

エラーとEnum• map in Result<T>

30Ⓒ Classmethod, Inc.

// NSErrorの可能性をもった値を let someInt = Result(127)

// NSErrorの可能性を保ったまま中身だけ変更 someInt.map { val in val + 1 }

エラーの可能性を保って中の値をマッピングする

エラーとEnum

31Ⓒ Classmethod, Inc.

Success

Failure

Result<T> Result<U>map(f: T -> U)

T

NSError

U

NSError

f

• map in Result<T>

エラーとEnum• flatMap in Optional<T> (Swift 1.2)

32Ⓒ Classmethod, Inc.

// Noneの可能性をもった値を let someStr: String? = “127”

// 中身があったら新しい // Noneの可能性をもった値に変更 let someInt = someStr?.toInt()

Optional Chaining

エラーとEnum• flatMap in Optional<T> (Swift 1.2)

33Ⓒ Classmethod, Inc.

// Noneの可能性をもった値を let someInt = “127”.toInt()

// 中身があったら新しい // Noneの可能性をもった値に変更する someInt.flatMap { val as? UInt8 }

// Noneの可能性をもった値を let someStr: String? = “127”

// 中身があったら新しい // Noneの可能性をもった値に変更 let someInt = someStr.flatMap { str in str.toInt() }

エラーとEnum• flatMap in Optional<T> (Swift 1.2)

34Ⓒ Classmethod, Inc.

// Noneの可能性をもった値を let someInt = “127”.toInt()

// 中身があったら新しい // Noneの可能性をもった値に変更する someInt.flatMap { val as? UInt8 }

// Noneの可能性をもった値を let someStr: String? = “127”

// 中身があったら新しい // Noneの可能性をもった値に変更 let someInt = someStr.flatMap(String.toInt)

無引数インスタンスメソッドを関数として扱う

エラーとEnum

35Ⓒ Classmethod, Inc.

Some

None

Optional<T>

T

None

f

flatMap(f: T -> Optional<U>)

Optional<U>

U

None

• flatMap in Optional<T> (Swift 1.2)

エラーとEnum

36Ⓒ Classmethod, Inc.

extension Result { func flatMap<U>(f: T -> Result<U>) -> Result<U> { switch self { case .Success(let box) : return f(box.value) case .Failure(let err) : return .Failure(err) } } }

新しいエラー可能性を持つ値にマッピングしてマージ• flatMap in Result<T>

エラーとEnum• flatMap in Result<T>

37Ⓒ Classmethod, Inc.

// NSErrorの可能性をもった値を let someInt = Result(127)

// 中身があったら新しい // 新しいNSErrorの可能性をもった値に変更 someInt.flatMap{ val in Result(val + 1)}

新しいエラー可能性を持つ値にマッピングしてマージ

エラーとEnum

38Ⓒ Classmethod, Inc.

Success

Failure

Result<T>

T

NSError

f

flatMap(f: T -> Result<U>)

Result<U>

U

NSError

• flatMap in Result<T>

39Ⓒ Classmethod, Inc.

Enumでエラーの可能性を持つ 値を作る

40Ⓒ Classmethod, Inc.

Topics for today

• Enum

• Collection API

• BrightFutures

41Ⓒ Classmethod, Inc.

Collection API

• 実践 CollectionAPI

• map, filter, reduce

• flatMap(Swift 1.2)

42Ⓒ Classmethod, Inc.

let names = users.map { user in user.name } println(names)

実践 CollectionAPI

• Swiftのコードでfor文は一回も使わなかった

43Ⓒ Classmethod, Inc.

var names = [String]() for user in users { names.append(user.name) } println(names)

マッピングのたびに コード領域に変数が

一つ加わる

変数という状態を 考えずに済む

let totalAge = users.reduce(0) { total, user in total + user.age } println(totalAge)

実践 CollectionAPI

• Swiftのコードでfor文は一回も使わなかった

44Ⓒ Classmethod, Inc.

var totalAge = 0 for user in users { totalAge += user.age } println(totalAge)

集計の際に変数1つが コード領域に加わる

集計の際に変数は 考えなくていい

実践 CollectionAPI

• コード量を大幅に削減できた

45Ⓒ Classmethod, Inc.

- (NSArray *)namesForUsers:(NSArray *)users { NSMutableArray *names = [NSMutableArray new]; for(User *user in users) { [names addObject:user.name]; } return [NSArray arrayWithArray:names]; }

のようなマッピング用のメソッド、処理がmap一つで書ける

実践 CollectionAPI

• メモリ管理のためにunownedやweakを使う

46Ⓒ Classmethod, Inc.

users.map { [weak self] user in self?.names.append(user.name) return Void() }

• map等のクロージャでselfを使う場合は  循環参照の可能性からweak, unownedを考慮する

• 設計を考え直す

47Ⓒ Classmethod, Inc.

関数フローを用い コード領域から

できるだけ状態を排する

Collection API

• 実践 CollectionAPI

• map, filter, reduce

• flatMap(Swift 1.2)

48Ⓒ Classmethod, Inc.

map, filter, reduce• map

49Ⓒ Classmethod, Inc.

Array<T> Array<U>

map(transform: T -> U)T U

T

T

U

U

transform

map, filter, reduce• filter

50Ⓒ Classmethod, Inc.

Array<T> Array<T>

filter(includeElement: T -> Bool)T

T

T

true/falseT

T

includeElement

map, filter, reduce• reduce

51Ⓒ Classmethod, Inc.

Array<T> reduce(initial: U, combine: (U, T) -> U)

T

combine

T

T

U

U

initial

map, filter, reduce• 文字列の配列のうち、二文字以上のものを選び、  文字数のトータルを集計する

52Ⓒ Classmethod, Inc.

BeforeNSArray *strings = [@“aaaa”, @“bbb”, @“c”, @“dd”];NSUInteger totalLengthForStrings = 0;for(NSString *string in strings) { NSUInteger lengthOfString = string.length if (lengthOfString >= 2) { totalLengthForStrings += lengthOfString }}NSLog(@“%ld”, totalLengthForStrings) // 9

map, filter, reduce• 文字列の配列のうち、二文字以上のものを選び、  文字数のトータルを集計する

53Ⓒ Classmethod, Inc.

After

let strings = [“aaaa”, “bbb”, “c”, “dd”]let totalCount = strings.map(countElements) .filter { count in count >= 2 } .reduce(0, +)println(totalCount) // 9

54Ⓒ Classmethod, Inc.

コレクションを 関数フローで

整形・選択・集計する

Collection API

• 実践 CollectionAPI

• map, filter, reduce, sorted, first, last

• flatMap(Swift 1.2)

55Ⓒ Classmethod, Inc.

56Ⓒ Classmethod, Inc.

flatMap(Swift 1.2)

let strings = [“aaaa”, “bbb”]let bothCaseStrings = strings.flatMap { str in [str.uppercaseString, str.lowercaseString] }println(bothCaseStrings) // [“aaaa”, “AAAA”, “bbb”, “BBB”]

57Ⓒ Classmethod, Inc.

flatMap(Swift 1.2)

Array<T> Array<U>

flatMap(transform: T -> [U])T U

transformT

T

U

U

U

U

U

58Ⓒ Classmethod, Inc.

Topics for today

• Enum

• Collection API

• BrightFutures

59Ⓒ Classmethod, Inc.

BrightFuture• Futures in Swift

• Future

• Promise

• メソッド概観

• Pluggable Activities

60Ⓒ Classmethod, Inc.

• Swiftz https://github.com/typelift/swiftz

• BrightFutures https://github.com/Thomvis/BrightFutures

61Ⓒ Classmethod, Inc.

Futures in Swift

• Swiftz

関数型プログラミングインターフェイスをSwiftに追加

62Ⓒ Classmethod, Inc.

Futures in Swift

• Swiftz

多くの演算子はHaskellやF#から由来している中には • (Option+8) などの特殊文字もある

63Ⓒ Classmethod, Inc.

Futures in Swift

• Swiftz

非同期を扱う為のクラスFutureも抽象度の高い基底クラスを有するため採用見送り

64Ⓒ Classmethod, Inc.

Futures in Swift

• Swiftz

非同期を扱う為のクラスFutureも抽象度の高い基底クラスを有するため採用見送り

65Ⓒ Classmethod, Inc.

Futures in Swift

66Ⓒ Classmethod, Inc.

BrightFutures

BrightFuture• Futures in Swift

• Future

• Promise

• メソッド概観

• Pluggable Activities

67Ⓒ Classmethod, Inc.

68

コールバックハンドラでの 非同期処理

69Ⓒ Classmethod, Inc.

Future• Objective-Cでの典型的な非同期ハンドリング

[[SomeTaskManager sharedInstance] taskWithCompletion:^(NSData *data, NSError *err) { if (err) { // エラーハンドリング } else { // エラーがない時の処理 } }];

70Ⓒ Classmethod, Inc.

Future

• エラーハンドリングについてはほぼ定形の処理

• エラーが無い時に続けて簡単に処理を書きたい…

• 非同期処理がまたがる度にネストが深くなっていく

71Ⓒ Classmethod, Inc.

Future

• エラーハンドリングについてはほぼ定形の処理

• エラーが無い時に続けて簡単に処理を書きたい…

• 非同期処理がまたがる度にネストが深くなっていく

そのお悩み

Future<T>が

解決します!!!

72Ⓒ Classmethod, Inc.

public class Future<T> { var result: Result<T>? = nil

... }

非同期処理エラーの可能性 を持った値

Future

public enum Result<T> { case Success(Box<T>) case Failure(NSError) }

エラーの可能性 を持った値

Result<T>は実装に入っている

73Ⓒ Classmethod, Inc.

Future

Success

Failure

T

NSError

Pending None

Some(Result<T>)

• Futureの取りうる状態は3つ

非同期処理中

非同期処理完了

74Ⓒ Classmethod, Inc.

Future• Futureをつくる

public func future<T>( context c: ExecutionContext = Queue.global, task: () -> T ) -> Future<T>

let futureValue = future { // 何か重い処理 return 返り値 }

必要に応じてキューを指定する (Queueはdispatch_queue_tのラッパー)

75

Futureは非同期エラーの 可能性をもった値

BrightFuture• Futures in Swift

• Future

• Promise

• メソッド概観

• Pluggable Activities

76Ⓒ Classmethod, Inc.

77Ⓒ Classmethod, Inc.

Promise

• Future<T>の生成に利用

func someTask() -> Future<NSData> { let promise = Promise<NSData>() SomeTaskManager.sharedInstance .taskWithCompletion { data, err in if err != nil { // エラーハンドリング promise.failure(err) } else { // エラーがない時の処理 promise.success(data) } } return promise.future }

78Ⓒ Classmethod, Inc.

Promise

• 「約束」「契約」を表すオブジェクト

func someTask() -> Future<NSData> { let promise = Promise<NSData>() SomeTaskManager.sharedInstance .taskWithCompletion { data, err in if err != nil { // エラーハンドリング promise.failure(err) } else { // エラーがない時の処理 promise.success(data) } } return promise.future }

コールバックが一回きり呼ばれる 契約を表す

Futureをエラーで 完了させる

Futureを成功で 完了させる

79Ⓒ Classmethod, Inc.

Promise• 非同期処理が呼ばれた証として Future<T>オブジェクトを発行

func someTask() -> Future<NSData> { let promise = Promise<NSData>() SomeTaskManager.sharedInstance .taskWithCompletion { data, err in if err != nil { // エラーハンドリング promise.failure(err) } else { // エラーがない時の処理 promise.success(data) } } return promise.future }

Futureを発行する

80Ⓒ Classmethod, Inc.

BoltsFramework

Promise

81Ⓒ Classmethod, Inc.

• BrightFutures と BoltsFrameworkの比較

- (BFTask *)someTask { BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource]; [[SomeTaskManager sharedInstance] taskWithCompletion:^(NSData *data, NSError *err) { if (err) { [task setError:err]; } else { [task setResult:data]; } }]; return task.task; }

Promise

Sourseの生成

完了処理

BFTaskをSourceから生成

82Ⓒ Classmethod, Inc.

• BrightFutures と BoltsFrameworkの比較

func someTask() -> Future<NSData> { let promise = Promise<NSData>() SomeTaskManager.sharedInstance() .taskWithCompletion { data, err in if err != nil { promise.failure(err) } else { promise.success(data) }

} return promise.future }

Promise

Promiseの生成

完了処理

FutureをPromiseから生成

83Ⓒ Classmethod, Inc.

Promise

• Promiseは発行したら必ず成功かエラーを一回だけ発行するように実装

• Promiseに複数の成功を送るとランタイムエラーを起こす

• 複数回呼ばれるようなDelegateとは相性が悪い

84

コールバックハンドラには Promiseを用いてFutureを生成

BrightFuture• Futures in Swift

• Future

• Promise

• メソッド概観

• Pluggable Activities

85Ⓒ Classmethod, Inc.

86Ⓒ Classmethod, Inc.

メソッド概観• onComplete in Future<T>

onComplete( callback: Result<T> -> Void ) -> Future<T>

T

NSError

None

Result<T>

callback

Future<T>

87Ⓒ Classmethod, Inc.

メソッド概観

futureTask.onComplete { result in switch result { case .Success(let val): () case .Failure(let err): () } }

非同期計算の結果にかかわらすResult<T> を返却

• onComplete in Future<T>

88Ⓒ Classmethod, Inc.

メソッド概観• onSuccess in Future<T>

onSuccess( callback: T -> Void ) -> Future<T>

T

NSError

None

Result<T>

callback

Future<T>

89Ⓒ Classmethod, Inc.

メソッド概観• onSuccess in Future<T>

futureTask.onSuccess { value in // T型のvalueに対する処理 }

非同期計算が成功した場合Tを返却

90Ⓒ Classmethod, Inc.

メソッド概観• onFailure in Future<T>

onFailure( callback: NSError -> Void ) -> Future<T>

T

NSError

None

Result<T>

Future<T>

callback

91Ⓒ Classmethod, Inc.

メソッド概観

futureTask.onFailure { error in // NSError型のerrorに対する処理 }

非同期計算が成功した場合NSError を返却

• onFailure in Future<T>

92Ⓒ Classmethod, Inc.

メソッド概観• map in Future<T>

Future<T> Future<U>map(f: T -> U)

T

NSError

U

NSError

f

None

93Ⓒ Classmethod, Inc.

// 非同期エラーの可能性をもった値を let futureData = someTask() // Future<NSData>型の返り値 // 非同期エラーの可能性を保ったまま中身だけ変更 futureData.map { data in NSString( data: data, encoding: NSUTF8StringEncoding ) } // Future<NSString>型の返り値

メソッド概観• map in Future<T>

94Ⓒ Classmethod, Inc.

メソッド概観• flatMap in Future<T>

Future<T> Future<U>flatMap(f: T -> Future<U>)T

NSError

U

NSError

f

None

95Ⓒ Classmethod, Inc.

// 非同期エラーの可能性をもった値を let someURLString = someURLTask()// Future<String>型の返り値 // 中身がエラーでなければ続きの // 非同期エラー可能性をもった値を評価 someURLString.flatMap(fetchData) // fetchData: Stringを引数にもち // Future<NSData>を返り値に持つ関数

メソッド概観• flatMap in Future<T>

96

非同期処理を定形的な ハンドリングで記述

BrightFuture• Futures in Swift

• Future

• Promise

• メソッド概観

• Pluggable Activities

97Ⓒ Classmethod, Inc.

• 非同期処理のまとまり毎にFutureを生成

98Ⓒ Classmethod, Inc.

Pluggable Activities

→非同期処理同士を抜き差し、順序入替しやすくなった

Web API Task App DB Task

Web API TaskApp DB Task

• 非同期処理のまとまり毎にFutureを生成

99Ⓒ Classmethod, Inc.

Pluggable Activities

Web API App DB HeavyTaskInfrastructure

Use case Authentication UserLogic

→ロジックが   非同期を要する処理かどうかがわかりやすくなった

→アクティビティ図とコードが対応するようになった100Ⓒ Classmethod, Inc.

Pluggable Activities

User Event

API Call

DB Update

NSError NSError Success

• 非同期処理のまとまり毎にFutureを生成

101

Futureでアカルイ非同期処理

102Ⓒ Classmethod, Inc.

103

おまけ

Topics for today

• Enum

• Collection API

• BrightFutures

104Ⓒ Classmethod, Inc.

Topics for today

• Enum

• Collection API

• BrightFutures

105Ⓒ Classmethod, Inc.

Optional<T>Result<T>

Array<T>

Future<T>

106

• Enum

• Collection API

• BrightFutures

107Ⓒ Classmethod, Inc.

Optional<T>Result<T>

Array<T>

Future<T>

map

map(f: T -> U) -> U?map(f: T -> U) -> Result<U>

map(f: T -> U) -> Array<U>

map(f: T -> U) -> Future<U>

• Enum

• Collection API

• BrightFutures

108Ⓒ Classmethod, Inc.

flatMap

Optional<T>Result<T>

Array<T>

Future<T>

flatMap(f: T -> U?) -> U?flatMap(f: T -> Result<U>) -> Result<U>

flatMap(f: T -> Array<U>) -> Array<U>

flatMap(f: T -> Future<U>) -> Future<U>

109Ⓒ Classmethod, Inc.

map ・ flatMapを 適用できる型を抽象化

XXX<T> map(f: T -> U) -> XXX<U>

flatMap(f: T -> XXX<U>) -> XXX<U>

Functor・Applicative・Monadへ

110

111Ⓒ Classmethod, Inc.

Haskell・Scala等の 先達に根拠を求める

112Ⓒ Classmethod, Inc.

巨人の肩に乗る

Developer Day

スライドは後日ブログで公開します。

113

A-1

Ⓒ Classmethod, Inc.

#cmdevio2015