Swift 2.0 の Error Handling #yhios

Preview:

Citation preview

EZ-‐‑‒NET  熊⾕谷友宏  http://ez-‐‑‒net.jp/

Swift 2.0  の

2015.06.20  @  横浜へなちょこ  iOS  勉強会  #35

ERROR HANDLING

熊谷友宏http://ez-net.jp/ @es_kumagai

Xcode 5 徹底解説

IP Phone 音でダイヤル 音で再配達ゴッドいつもの電卓 with 割勘ウォッチ

MOSA

Error Handling ってなに?

NSError を使いやすくするってコト!

エラーの話

これまでの NSErrorSwift 1.2

たとえば NSFileManager

これまでの NSErrorSwift 1.2

// オプショナルな NSError を状態として用意 var error:NSError? = nil

// 用意した NSError を inout で渡す let succeeded = fm.removeItemAtPath(path, error: &error)

// エラーを検査する if !succeeded, let error = error {

}

func removeItemAtPath( path:String, error: NSErrorPointer) -> BOOL

▶ 戻り値で目的の結果を返す ▶ エラーのときは

NSErrorPointer で詳細を返す ▶ つまりエラーの詳細を知りたければ

NSError? を事前に別途用意する

これまでの NSErrorSwift 1.2

func contentsOfDirectoryAtPath( path:String, error: NSErrorPointer) -> [AnyObject]?

▶ 戻り値で目的の結果を返す ▶ コンテンツが無ければ空の配列を返す ▶ エラーがあったときは

▶ 戻り値で nil を返す ▶ NSErrorPointer で詳細情報を返す

これまでの NSErrorSwift 1.2

これからの NSErrorSwift 2.0

これからの NSErrorSwift 2.0

// 正常処理のスコープを決める do {

// 目的をまっすぐ達成する try fm.removeItemAtPath(path) } catch let error as NSError {

// エラーならここで処理する }

func removeItemAtPath( path:String) throws -> Void

▶ 目的を遂行する ▶ エラーが発生するかもネ!

これからの NSErrorSwift 2.0

func contentsOfDirectoryAtPath( path:String) throws -> [String]

▶ 戻り値で目的の結果を返す ▶ コンテンツが無ければ空の配列を返す ▶ エラーが発生するかもネ!

これからの NSErrorSwift 2.0

正常系とエラー系とを分離Error Handling で

ところでこれまでの NSError は

これまでの NSErrorSwift 1.2

// NSError を用意しなくても実行可能 fm.removeItemAtPath(path, error: nil)

▶ 成否を戻り値だけで判定する ▶ 成功したものとして突き進むも可能

それって安全じゃない

Swift はそんなことはさせません

Swift は「安全」がお好き

たとえば

func setAttributes(attributes, ofItemAtPath:path) throws

Error HandlingSwift 2.0

fm.setAttributes(attr, ofItemAtPath:path) エラーを未想定ならエラー

Error HandlingSwift 2.0

try fm.setAttributes(attr, ofItemAtPath:path) エラーを想定!

Error HandlingSwift 2.0

try fm.setAttributes(attr, ofItemAtPath:path) エラーを想定!

正常系はこれ以降のスコープ全体

Error HandlingSwift 2.0

do {

try fm.setAttributes(attr, ofItemAtPath:path)

}

正常系のスコープを明記!

Error HandlingSwift 2.0

do {

try fm.setAttributes(attr, ofItemAtPath:path)

}

正常時の処理をこの中で決着する

正常系のスコープを明記!

Error HandlingSwift 2.0

do {

try fm.setAttributes(attr, ofItemAtPath:path)

} catch let error as NSError {

}

エラーが発生したらキャッチ!

Error HandlingSwift 2.0

do {

try fm.setAttributes(attr, ofItemAtPath:path)

} catch let error as NSError {

}

エラーが発生したらキャッチ!

エラー時の処理をこの中で決着する

Error HandlingSwift 2.0

do {

try fm.setAttributes(attr, ofItemAtPath:path)

} catch let error as NSError {

}

エラーが発生したらキャッチ!

エラー時の処理をこの中で決着する

エラー時はここは処理されない

まとめると

Error HandlingSwift 2.0

do {

try fm.setAttributes(attr, ofItemAtPath:path)

} catch let error as NSError {

}

エラーが発生したらキャッチ!

エラー時の処理をこの中で決着する

エラー時はここは処理されない

正常時の処理をこの中で決着する

正常系のスコープを明記!

エラーを想定!

つまり

エラーを確実に扱えるってコト!

next

絶対エラーにならなくない?

絶対にエラーにならない場合だってあるかもしれない

これまでの NSErrorSwift 1.2

これまでの NSErrorSwift 1.2

// エラーチェックを記載しない fm.setAttributes(attr, ofItemAtPath:path, error:nil)

成否に関わらず以下が実行される

Error HandingSwift 2.0

Error HandlingSwift 2.0

// エラーはない ! と明記する try! fm.setAttributes(attr, ofItemAtPath:path)

エラーのときは以下に進まず 強制終了

つまり

無視するにも覚悟が要るってコト!

next

エラーのときの後始末

Error Handlingdo { let handle = try file.open()

try fm.setAttributes(attr, ofItemAtPath:path)

handle.close()

処理が終わったら閉じたい

} catch {

}

でもエラーが発生すると…

ここまでたどり着けない

Error Handlingdo { 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 ErrorSwift でも積極的に使われている

// 強制アンラップ let value = optional!

// 暗黙アンラップなオプショナル var value:String!

値がなければ fatalError

nil が入っているのに操作したら fatalError

Fatal ErrorSwift でも積極的に使われている

// 強制キャスト let subObj = obj as! SubClass

// エラーを想定しない try! execute()

キャストできなければ fatalError

エラーが起これば fatalError

Fatal Error で想定外を想定内へ転換する

Swift の Error HandlingNew!

エラー型の定義

Error Handling

protocol ErrorType {

}

▶ エラー型を ErrorType で表現 ▶ 実装を求められないプロトコル ▶ 列挙型と NSError が準拠できる

ErrorType プロトコル

Error Handling

enum OpenError : ErrorType {

case NotFound case Readonly case Busy(reason:String) }

▶ エラー型は 列挙型 で表現 ▶ 列挙型を ErrorType に準拠させる ▶ 起こり得るエラーを列記

値付き列挙子も使える

Error 型

関数やメソッドで使う

Error Handling

func open(file:FILE) throws -> Stream { : :

▶ エラーが有り得る機能に throws を付与 ▶ エラーになるかもしれないことが

プログラマーとコンパイラの両者に伝わる

エラーを示唆する

Error Handling

func open(file:FILE) throws -> Stream {

guard _exists(file) else {

throw OpenError.NotFound } : :

▶ エラーは throw で通知する ▶ エラーは列挙子で指定する ▶ throws を指定した機能でだけ通知可能

エラーを通知する

確実にエラーを想定使う側は

Error Handling

do { let stream = try open(file)

} catch { }

▶ 正常処理の範囲を do で表現 ▶ エラーが発生し得る場所に try を明記 ▶ エラーは 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 からの自動変換

// このような Objective-C コードが - (NSString*)getNameFromPath:(NSString*)path error:(NSError**)error;

// このような Swift コードになる func getNameFromPath(path:String!) throws -> String

▶ 戻り値がクラスの場合 ▶ 最後の引数が NSError** の場合

末尾の NSError を throws に変換

Objective-C からの自動変換

// このような Objective-C コードが - (BOOL)prepareWithOptions:(NSArray*)opts error:(NSError**)error;

// このような Swift コードになる func prepareWithOptions(opts:[AnyObject]!) throws -> Void

▶ 戻り値が BOOL の場合 ▶ 最後の引数が NSError** の場合

末尾の NSError を throws に変換

Objective-C への自動変換

Objective-C への自動変換

// このような Swift コードが func getName(path:String) throws -> String

// このような Objective-C コードになる - (NSString*)getName:(NSString*)path error:(NSError**)error;

▶ 戻り値が @objc 互換オブジェクトの場合 ▶ throws が指定されている場合

throws を NSError に変換

Objective-C への自動変換

// このような Swift コードが func prepare(options:[String]?) throws

// このような Objective-C コードになる - (BOOL)prepare:(NSArray**)options error:(NSError**)error;

▶ 戻り値が Void の場合 ▶ throws が指定されている場合

throws を NSError に変換

安心してError Handling を活用できる

つまり

Swift は かっこいいってコト!

おしまい。