LoginSignup
0
2

More than 3 years have passed since last update.

【Swift】do-catch文でエラー処理を行う(その2)

Last updated at Posted at 2020-12-26

この記事は、【Swift】do-catch文でエラー処理を行う(その1)の続きです。

前回の記事では、do-catch文の基本的な実装方法と
throwsキーワード, rethrowsキーワードに関して記載しました。
これらのキーワードを聞いて全くピンと来ない方はぜひご覧ください!

では続きになります。

tryキーワード

tryキーワードは、エラーを発生させる可能性のある処理を実行する際に追加します。
つまり、throwsキーワードが指定された処理を呼び出す場合です。

それらの処理の呼び出し前にtryキーワードを追加して、
try 関数名(引数)のように記述します。

次のサンプルコードでは、
Int型のMax値の半分以下の値が引数に渡されたら、その値を2倍にして返し、
Max値の半分以上の値だった場合はエラー処理を行っています。

guard文の中にreturnがないではないか!と思うかもしれませんが、
必ずしもreturnが必要なわけではなくthrowでも大丈夫だそうです!

値を返すのではなく、エラーを返しているイメージですかね。

今回は、100を渡すパターンと、Int型のMax値を渡しています。
後者はもちろんguard文でひっかかりますのでエラー処理になります。


enum SomeError: Error {
    case overInt
}

func double(value: Int) throws -> Int {
    guard value <= Int.max / 2 else {
        throw SomeError.overInt
    }

    return value * 2
}

do {
    let value1 = try double(value: 100)
    print(value1)
    let value2 = try double(value: Int.max)
    print(value2)
} catch {
    print("Error: \(error)")
}

実行結果
200
Error: overInt

try!キーワード

出たよと思ってしまうくらい「!」は使われますよね・・・。

try!以外での!キーワードの使い道としては、
Optional<Wrapped>型の強制アンラップとかが有名ですかね。

try!キーワードも結構強気なタイプです(笑)

throwsキーワードを指定されている処理はtryキーワードを追加すると先ほど書きました。

しかし、特定の場面では絶対にエラーが発生しないと分かっていて、
わざわざエラー処理を記述したくないケースもあると思います。

その場合に使えるのがtry!キーワードです。
tryの代わりにtry!にするとエラーを無視できます。

また、try!キーワードはエラーを無視するので、
do節内やthrowsキーワードが指定された処理の内部でなくても使用できます。

先ほどのサンプルコードの内容を一部変更しました。
value1は上手くいっていますが、value2で実行時エラーが発生しています。


enum SomeError: Error {
    case overInt
}

func double(value: Int) throws -> Int {
    guard value <= Int.max / 2 else {
        throw SomeError.overInt
    }

    return value * 2
}


let value1 = try! double(value: 100)
print(value1)
let value2 = try! double(value: Int.max)   // 実行時エラー
print(value2)

実行結果
200
Fatal error: 'try!' expression unexpectedly raised an error: __lldb_expr_297.SomeError.overInt: file __lldb_expr_297/MyPlayground.playground, line 17

try!キーワードは、Swift自慢の安全性を損ねるのでは?と思いましが、よく考えると、
いかなる場合も全てのエラーケースに対処するのはあまりにも面倒だなとも思います。
それに現実的ではありませんし可読性も悪くなります。

かといって暗黙的にエラーが無視できると考えると、
どこでエラーが無視されたのかわからず、
結果として信頼性の低いプログラムにもなります。

ですが、「!」がついている箇所が暗黙的にエラーを無視する箇所とわかるので、
結果としてtry!キーワードは実用性と安全性をバランスよく保っています。

try?キーワード

throwsキーワードが指定された処理であっても、
利用時にはエラーの詳細が不要な場合もあります。
try?キーワードはそのような時に使用します。

try!キーワードは、エラーは絶対に発生しない時に使い。
try?キーワードは、エラーは発生するが詳細は不要な時に使うイメージです。

try?キーワードをつけて処理を呼び出すとdo-catch文を省略することができます。
しかし、その代わりに関数の戻り値がOptional<Wrapped>型になります。

つまり、Optional<Wrapped>型によるエラー処理と同様になります。

tryキーワードとの相違点としては、
do-catch文のdo節やthrowsキーワードが指定された内部でなくても使用できるところです。

次のサンプルコードでは、
引数の値を2倍にして返す関数を定義しています。

②のif let value = try? double(value: Int.max){}は、
guard文で引っ掛かりthrow SomeError.overIntが実行されますが、
実際にvalueに入る値はnilになります。


enum SomeError: Error {
    case overInt
}

func double(value: Int) throws -> Int {
    guard value <= Int.max / 2 else {
        throw SomeError.overInt
    }

    return value * 2
}

// ①
if let value = try? double(value: 2) {
    print(value)
} else {
    print("error")
}

// ②
if let value = try? double(value: Int.max) {
    print(value)
} else {
    print("error")
}

実行結果
4
error

また、try?キーワードはイニシャライザに対しても利用できるので、
失敗可能イニシャライザのように扱うこともできます。


enum SampleEnum: Error {
    case outOfRange
}

struct SampleStruct {
    var value: Int

    init(value: Int) throws {
        guard case 10...19 = value else {
            throw SampleEnum.outOfRange
        }
        self.value = value
    }
}

if let value = try? SampleStruct(value: 13) {
    print(value)
} else {
    print("error")
}

if let value = try? SampleStruct(value: 20) {
    print(value)
} else {
    print("error")
}

実行結果
SampleStruct(value: 13)
error

defer文によるエラーの有無に関わらない処理

defer文を使用すれば、エラーが発生しようと発生しなかろうと処理を行うことができます。
記述方法はとても簡単で実行したい箇所に追加するだけです。

まず、defer文を使わない場合の処理がこちらになります。

try sampleFunc()で必ずエラーが発生するので、
hello()関数は絶対に実行されません。


enum SampleError: Error {
    case someError
}

func sampleFunc() throws {
    print("sampleFunc()実行")
    throw SampleError.someError
}

func hello() {
    print("こんにちは")
}

do {
    try sampleFunc()
    hello()
} catch {
    print("Error: \(error)")
}

実行結果
sampleFunc()実行
Error: someError

次がdefer文を使用した時の処理です。


enum SampleError: Error {
    case someError
}

func sampleFunc() throws {
    print("sampleFunc()実行")
    throw SampleError.someError
}

func hello() {
    print("こんにちは")
}

do {
    defer {
        hello()
    }
    try sampleFunc()
} catch {
    print("Error: \(error)")
}

実行結果
sampleFunc()実行
こんにちは
Error: someError

defer文の使えるところは、
do節が終わった後に実行されるというところです。

最後に行ってほしい処理があった際には便利そうですね!

以上でtryキーワード関連の説明は終了したいと思います。
do-catchは難しいですがぜひ使えるようになりたいです・・・。

続きはtryキーワードの利用タイミングについて説明していますのでぜひご覧ください。
-> 【Swift】do-catch文でエラー処理を行う(その3)

また、他のエラー処理についても記事にしているのでぜひご覧ください。
【Swift】Optional<Wrapped>型でエラー処理を行う
【Swift】Result<Success, Failure>型でエラー処理を行う
【Swift】fatalError関数によるプログラムの終了
【Swift】アサーションによるプログラムの終了

最後までご覧いただきありがとうございました。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2