>100 Views
June 21, 15
スライド概要
Swift 2.0 で新登場した Error Handling についての特徴や使い方などを、これまでの Swift 1.x にもあったその他のエラー処理の方法と合わせて紹介してみました。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 6,804 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
Swift 2.0 の ERROR HANDLING 2015.06.20 @ 横浜へなちょこ iOS 勉強会 #35 EZ-‐‑‒NET 熊⾕谷友宏 http://ez-‐‑‒net.jp/
熊谷友宏 http://ez-net.jp/ @es̲kumagai Xcode 5 徹底解説 MOSA IP Phone 音でダイヤル いつもの電卓 with 割勘ウォッチ 音で再配達ゴッド
Error Handling ってなに?
NSError を 使いやすくするってコト!
エラーの話
これまでの NSError Swift 1.2 たとえば NSFileManager
これまでの NSError Swift 1.2 // オプショナルな NSError を状態として用意 var error:NSError? = nil // 用意した NSError を inout で渡す let succeeded = fm.removeItemAtPath(path, error: &error) // エラーを検査する if !succeeded, let error = error { }
これまでの NSError Swift 1.2 func removeItemAtPath( path:String, error: NSErrorPointer) -> BOOL ▶ 戻り値で目的の結果を返す ▶ エラーのときは NSErrorPointer で詳細を返す ▶ つまりエラーの詳細を知りたければ NSError? を事前に別途用意する
これまでの NSError Swift 1.2 func contentsOfDirectoryAtPath( path:String, error: NSErrorPointer) -> [AnyObject]? ▶ 戻り値で目的の結果を返す ▶ コンテンツが無ければ空の配列を返す ▶ エラーがあったときは ▶ 戻り値で nil を返す ▶ NSErrorPointer で詳細情報を返す
これからの NSError Swift 2.0
これからの NSError Swift 2.0 // 正常処理のスコープを決める do { // 目的をまっすぐ達成する try fm.removeItemAtPath(path) } catch let error as NSError { // エラーならここで処理する }
これからの NSError Swift 2.0 func removeItemAtPath( path:String) throws -> Void ▶ 目的を遂行する ▶ エラーが発生するかもネ!
これからの NSError Swift 2.0 func contentsOfDirectoryAtPath( path:String) throws -> [String] ▶ 戻り値で目的の結果を返す ▶ コンテンツが無ければ空の配列を返す ▶ エラーが発生するかもネ!
Error Handling で 正常系とエラー系とを分離
ところで これまでの NSError は
これまでの NSError Swift 1.2 // NSError を用意しなくても実行可能 fm.removeItemAtPath(path, error: nil) ▶ 成否を戻り値だけで判定する ▶ 成功したものとして突き進むも可能
それって安全じゃない
Swift は そんなことはさせません
Swift は「安全」がお好き
たとえば func setAttributes(attributes, ofItemAtPath:path) throws
Error Handling Swift 2.0 エラーを未想定ならエラー fm.setAttributes(attr, ofItemAtPath:path)
Error Handling Swift 2.0 エラーを想定! try fm.setAttributes(attr, ofItemAtPath:path)
Error Handling Swift 2.0 エラーを想定! try fm.setAttributes(attr, ofItemAtPath:path) 正常系はこれ以降のスコープ全体
Error Handling Swift 2.0 正常系のスコープを明記! do { try fm.setAttributes(attr, ofItemAtPath:path) }
Error Handling Swift 2.0 正常系のスコープを明記! do { try fm.setAttributes(attr, ofItemAtPath:path) 正常時の処理をこの中で決着する }
Error Handling Swift 2.0 do { try fm.setAttributes(attr, ofItemAtPath:path) } catch let error as NSError { エラーが発生したらキャッチ! }
Error Handling Swift 2.0 do { try fm.setAttributes(attr, ofItemAtPath:path) } catch let error as NSError { エラーが発生したらキャッチ! エラー時の処理をこの中で決着する }
Error Handling Swift 2.0 do { try fm.setAttributes(attr, ofItemAtPath:path) エラー時はここは処理されない } catch let error as NSError { エラーが発生したらキャッチ! エラー時の処理をこの中で決着する }
まとめると
Error Handling Swift 2.0 正常系のスコープを明記! do { エラーを想定! try fm.setAttributes(attr, ofItemAtPath:path) 正常時の処理をこの中で決着する エラー時はここは処理されない } catch let error as NSError { エラーが発生したらキャッチ! エラー時の処理をこの中で決着する }
つまり
エラーを確実に扱えるってコト!
next
絶対エラーにならなくない?
絶対にエラーにならない 場合だってあるかもしれない
これまでの NSError Swift 1.2
これまでの NSError Swift 1.2 // エラーチェックを記載しない fm.setAttributes(attr, ofItemAtPath:path, error:nil) 成否に関わらず以下が実行される
Error Handing Swift 2.0
Error Handling Swift 2.0 // エラーはない ! と明記する try! fm.setAttributes(attr, ofItemAtPath:path) エラーのときは以下に進まず 強制終了
つまり
無視するにも 覚悟が要るってコト!
next
エラーのときの後始末
Error Handling do { 処理が終わったら閉じたい let handle = try file.open() try fm.setAttributes(attr, ofItemAtPath:path) でもエラーが発生すると… ここまでたどり着けない handle.close() } catch { }
Error Handling do { 処理が終わったら閉じたい let handle = try file.open() 最後に処理したいことを先に書く defer { handle.close() } try fm.setAttributes(attr, ofItemAtPath:path) ここでエラーが発生しても… ブロックを抜ける直前に処理される } catch { }
余談
もしも Swift の Error Handling が @try-catch-finally だったとしたら
もし try-finally だったとしたら… var stream:Stream? = nil @try { 初期化と後始末のスコープ分断を考慮して外側に定義 stream = Stream.open(path) fm.setAttributes(attr, ofItemAtPath:path) そもそも、どこでエラーが起こるの…? } @catch let error { } @finally { 未初期化を考慮してオプショナルチェイニング stream?.close() }
つまり
こうではなく var stream:Stream? = nil @try { stream = Stream.open(path) fm.setAttributes(attr, ofItemAtPath:path) } @catch let error { } @finally { stream?.close() } 入れ物の事前準備が必要 ▶ 3つのブロックに着目 ▶ 流れよりも構文が主役 ▶ どこでエラーになるかが コードから読めない ▶
こうなる do { let handle = try file.open() defer { handle.close() } try fm.setAttributes(attr, ofItemAtPath:path) } catch { } 事前準備が不要 ▶ 流れが主役 原則成功、ときどき失敗 ▶ コードからエラーが 発生する箇所がわかる ▶
Error Handling は 美しいってコト!
next
Swift でエラーを扱う方法
Optional<T>
Optional<T> 値があるかないかを扱う型 ▶ 値の有無による判断を強制 ▶ 値がないときがエラーとは限らない if let value = optional { } else { // 値がなかったときにエラーとするかは状況次第 }
Optional のイメージ ?
Optional<T> 使いどころ ▶ 単純に値の有無を提示する ▶ エラーかどうかを決め付けない // 例えば、リストの中から値を検索する関数 func find(list, value) -> Index? { }
いわゆる Either 型
いわゆる Either 型 どちらかの状況を表現する型 ▶ 成功値か失敗値かを取る列挙型 ▶ 成否というより状況の切り分けに着目 enum Result<T> { case Success(T) case Failure(Error) }
いわゆる Either 型 どちらかの状況を表現する型 ▶ 戻り値ひとつで状況の切り分けが可能 ▶ 両者を同じ重みで扱う switch getResult() { case .Success(let value): case .Failure(let error): }
Either 型のイメージ
いわゆる Either 型
使いどころ
▶ 戻り値で二つの場面を提示する
▶ 背反する分岐点を表現する
// 成功したか失敗したかで進路を分岐する
enum Result<T,U> {
case Succeeded<T>
case Failed<U>
}
switch getResult() {
case .Success(let value):
case .Failure(let error):
}
Fatal Error
Fatal Error 致命的なエラー ▶ 処理が継続できない状況を表現 ▶ 想定外を持ち越さない fatalError("もうムリ…") 以降の処理は実行させない!
Fatal Error のイメージ
Fatal Error 使いどころ ▶ Optional で絶対に値が入っているとき ▶ Optional で値が入っていないと困るとき ▶ try が絶対にエラーにならないとき ▶ 処理を継続できないと判断したとき ▶ 相手に責任を取らせたいとき などなど 活用の場面は幅広い
実際
Fatal Error Swift でも積極的に使われている // 強制アンラップ let value = optional! 値がなければ fatalError // 暗黙アンラップなオプショナル var value:String! nil が入っているのに操作したら fatalError
Fatal Error Swift でも積極的に使われている // 強制キャスト let subObj = obj as! SubClass キャストできなければ fatalError // エラーを想定しない try! execute() エラーが起これば fatalError
Fatal Error で 想定外を想定内へ転換する
New! Swift の Error Handling
エラー型の定義
Error Handling ErrorType プロトコル ▶ エラー型を ErrorType で表現 ▶ 実装を求められないプロトコル ▶ 列挙型と NSError が準拠できる protocol ErrorType { }
Error Handling Error 型 ▶ エラー型は 列挙型 で表現 ▶ 列挙型を ErrorType に準拠させる ▶ 起こり得るエラーを列記 enum OpenError : ErrorType { case NotFound case Readonly case Busy(reason:String) } 値付き列挙子も使える
関数やメソッドで使う
Error Handling エラーを示唆する ▶ エラーが有り得る機能に throws を付与 ▶ エラーになるかもしれないことが プログラマーとコンパイラの両者に伝わる func open(file:FILE) throws -> Stream { : :
Error Handling エラーを通知する ▶ エラーは throw で通知する ▶ エラーは列挙子で指定する ▶ throws を指定した機能でだけ通知可能 func open(file:FILE) throws -> Stream { guard _exists(file) else { throw OpenError.NotFound } : :
使う側は 確実にエラーを想定
Error Handling エラーを想定する ▶ 正常処理の範囲を do で表現 ▶ エラーが発生し得る場所に try を明記 ▶ エラーは catch で補足する do { let stream = try open(file) } catch { } エラーに挑む!
Error Handling 全てのエラーに対処する do { let stream = try open(file) } catch OpenError.NotFound { } catch OpenError.ReadOnly { } catch OpenError.Busy(let reason) { }
Error Handling 全てのエラーに対処する do { let stream = try open(file) } catch OpenError.Busy { 値付き列挙子の値を加味しないことも可能 } catch is OpenError { 列挙子を加味せずに捕獲することも可能 }
おさらい
Error Handling おさらい ▶ エラーを列挙型で表現 起こり得るエラーが一目瞭然 ▶ エラーの可能性を throws で示唆 プログラマーとコンパイラに意思が伝わる ▶ エラーが起こり得る箇所に try を明記 どこでエラーを想定しているかが明確 ▶ エラーを想定したコードが必須 強制されると悩まずに済むので楽になる
つまり
Error Handling で とっても楽になるってコト!
Error Handling のイメージ
Error Handling 使いどころ ▶ 達成すべき目的があり それを達成できない可能性があるとき ▶ 原因が実行時エラーに限られるとき ▶ 原因がいくつか考えられるとき ▶ 原因を提示し、対応を求めたいとき // 目的が明確で、エラーも有り得る複合的な機能 func open(file:FILE) throws -> Stream
まとめ
エラーを扱う手段 ? Optional 型 ▶ 単純に値の有無を提示 いわゆる Either 型 ▶ 戻り値で分岐点を提示 Fatal Error ▶ 強制終了して根本的な改善を迫る Error Handling ▶ 目的を遂行できない時に原因を提示
Swift はこれらの使用を強要する 強要されると プログラミングが楽になる ?
つまり
Swift は すごいってコト!
おしまい。 ▶ Error Handling ってなに? ▶ NSError を使いやすくするってコト! ▶ エラーを確実に扱えるってコト! ▶ 無視するにも覚悟が要るってコト! ▶ Error Handling は美しいってコト! ▶ Error Handling でとっても楽になるってコト! ▶ Swift はすごいってコト!
おまけ
Objective-C のことも Swift は見捨てない
Objective-C からの自動変換
Objective-C からの自動変換 末尾の NSError を throws に変換 ▶ 戻り値がクラスの場合 ▶ 最後の引数が NSError** の場合 // このような Objective-C コードが - (NSString*)getNameFromPath:(NSString*)path error:(NSError**)error; // このような Swift コードになる func getNameFromPath(path:String!) throws -> String
Objective-C からの自動変換 末尾の NSError を throws に変換 ▶ 戻り値が BOOL の場合 ▶ 最後の引数が NSError** の場合 // このような Objective-C コードが - (BOOL)prepareWithOptions:(NSArray*)opts error:(NSError**)error; // このような Swift コードになる func prepareWithOptions(opts:[AnyObject]!) throws -> Void
Objective-C への自動変換
Objective-C への自動変換 throws を NSError に変換 ▶ 戻り値が @objc 互換オブジェクトの場合 ▶ throws が指定されている場合 // このような Swift コードが func getName(path:String) throws -> String // このような Objective-C コードになる - (NSString*)getName:(NSString*)path error:(NSError**)error;
Objective-C への自動変換 throws を NSError に変換 ▶ 戻り値が Void の場合 ▶ throws が指定されている場合 // このような Swift コードが func prepare(options:[String]?) throws // このような Objective-C コードになる - (BOOL)prepare:(NSArray**)options error:(NSError**)error;
安心して Error Handling を活用できる
つまり
Swift は かっこいいってコト!
おしまい。