WWDC 旅行の余談と Swift Open Hours 3 - Swift ラボで聞いてきた話 #cocoa_kansai
続・ゲンバのSwift
-
Upload
yuichi-adachi -
Category
Engineering
-
view
3.489 -
download
0
Transcript of 続・ゲンバのSwift
2
安達 勇一• 2013/12 入社
• TwitterID: @UsrNameu1
• Github: https://github.com/UsrNameu1
• Blog: http://dev.classmethod.jp/author/yad
• で連載やってます
離散値と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
• 対応するモデル
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”)}
リソースと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
エラーと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>
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を考慮する
• 設計を考え直す
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
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
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
多くの演算子はHaskellやF#から由来している中には • (Option+8) などの特殊文字もある
63Ⓒ Classmethod, Inc.
Futures in Swift
BrightFuture• Futures in Swift
• Future
• Promise
• メソッド概観
• Pluggable Activities
67Ⓒ Classmethod, Inc.
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のラッパー)
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を発行する
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とは相性が悪い
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>
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を生成
Topics for today
• Enum
• Collection API
• BrightFutures
105Ⓒ Classmethod, Inc.
Optional<T>Result<T>
Array<T>
Future<T>
• 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へ