SwiftでNSErrorを完璧に扱う10の方法

SwiftのロゴとNSErrorの文字を背景に、エラー処理のイメージを描写したイラストSwift
この記事は約18分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

プログラムを書いていると、想定外のエラーが発生することがあります。

それらのエラーをハンドリングし、適切にユーザーに伝えることは、ソフトウェアの品質を保つために非常に重要です。

Swiftでは、NSErrorを使ってこれらのエラーを効果的に取り扱うことができます。

NSErrorは、Objective-Cから継承されたエラーハンドリングの方法であり、Swiftでも使用することができます。

この記事では、NSErrorの基本から高度な使い方、注意点までを詳細に解説します。

●SwiftとNSErrorの基本

○Swift言語のエラー処理の特徴

Swiftには、do-catch文を使用したエラーハンドリングがあります。

このdo-catch文を使用することで、エラーの発生を予測し、それに対応するための処理を記述することができます。

また、SwiftにはErrorというプロトコルが存在し、カスタムエラータイプを定義することもできます。

しかし、特にObjective-Cとの互換性を保つために、NSErrorも利用されます。

enum CustomError: Error {
    case someError
}

func someFunction() throws {
    throw CustomError.someError
}

do {
    try someFunction()
} catch {
    print("エラーが発生しました。")
}

このコードでは、カスタムエラーの定義とdo-catch文を使用したエラーハンドリングの例を示しています。

この例では、someFunction関数内でエラーをスローし、その後のdo-catch文でそのエラーをキャッチしています。

○NSErrorの役割と基本的な概念

NSErrorは、エラー情報を表現するためのクラスです。

NSErrorは、エラードメイン(domain)、エラーコード(code)、およびエラー情報を含む辞書(userInfo)の3つの主要なプロパティを持っています。

これらの情報によって、エラーの原因や詳細な情報を取得することができます。

例として、ファイル読み込み時のエラーをNSErrorを用いて表現することを考えてみましょう。

let path = "/path/to/nonexistent/file"
do {
    let data = try Data(contentsOf: URL(fileURLWithPath: path))
} catch let error as NSError {
    print("エラードメイン: \(error.domain)")
    print("エラーコード: \(error.code)")
    if let description = error.userInfo[NSLocalizedDescriptionKey] as? String {
        print("エラー詳細: \(description)")
    }
}

このコードでは、存在しないファイルパスからデータを読み込もうとした場合のエラー処理を示しています。

この例では、エラーが発生した場合に、NSErrorの各プロパティを使用してエラーの詳細情報を取得しています。

●NSErrorの作成と取り扱い

NSErrorの取り扱いに慣れることで、Swiftプログラミングでのエラーハンドリングがより効果的になります。

ここでは、NSErrorの生成からその詳細な取り扱い方法まで、具体的なコード例とともに解説します。

○サンプルコード1:NSErrorを生成する基本的な方法

NSErrorオブジェクトの作成は直感的です。

エラードメイン、エラーコード、ユーザーインフォメーションディクショナリを指定してインスタンスを作成します。

下記のサンプルコードでは、NSErrorオブジェクトを作成し、その情報をコンソールに出力しています。

import Foundation

// NSErrorオブジェクトを作成
let error = NSError(domain: "com.example",
                    code: 404,
                    userInfo: [NSLocalizedDescriptionKey: "要求されたページが見つかりませんでした。"])

// エラー情報を出力
print("ドメイン: \(error.domain)")
print("コード: \(error.code)")
print("説明: \(error.localizedDescription)")

このコードではcom.exampleというエラードメイン、404というエラーコード、そしてエラーの説明を含むユーザーインフォメーションディクショナリを使用してNSErrorを生成しています。

コンソールには、指定したエラードメイン、エラーコード、エラーの説明が出力されます。

○サンプルコード2:カスタム情報を含むNSErrorの作成方法

NSErrorはカスタマイズが容易であり、userInfo辞書に任意の情報を追加することができます。

下記のコードは、NSErrorにカスタム情報を追加する方法を表しています。

import Foundation

// カスタムエラー情報を含むNSErrorオブジェクトを作成
let error = NSError(domain: "com.example",
                    code: 500,
                    userInfo: [NSLocalizedDescriptionKey: "サーバーエラーが発生しました。",
                               "CustomInfo": "特定のエラー情報"])

// カスタムエラー情報を出力
if let customInfo = error.userInfo["CustomInfo"] as? String {
    print("カスタムエラー情報: \(customInfo)")
}

この例では、”CustomInfo”キーに対して”特定のエラー情報”という文字列を設定しています。

このようにしてNSErrorのuserInfo辞書に任意のキーと値を追加することができます。

実行すると、「カスタムエラー情報: 特定のエラー情報」と出力されます。

○サンプルコード3:NSErrorからの情報の取得方法

NSErrorオブジェクトからエラー情報を取得する方法も知っておくと便利です。

下記のサンプルコードは、NSErrorからエラードメイン、エラーコード、エラー説明を取得する方法を表しています。

import Foundation

// NSErrorオブジェクトを作成
let error = NSError(domain: "com.example",
                    code: 400,
                    userInfo: [NSLocalizedDescriptionKey: "無効なリクエストです。"])

// エラー情報を取得
let domain = error.domain
let code = error.code
let description = error.localizedDescription

// エラー情報を出力
print("ドメイン: \(domain)")
print("コード: \(code)")
print("説明: \(description)")

このコードで作成したNSErrorオブジェクトから、エラードメイン、エラーコード、エラー説明を取得してコンソールに出力しています。

これらの情報は、エラーの原因を特定したり、ユーザーにエラー情報を表示する際に使用することができます。

このコードを実行すると、それぞれのエラー情報がコンソールに出力されます。

●NSErrorを活用する応用例

Swiftでのエラーハンドリングをさらに進化させるために、NSErrorのさまざまな応用例を学ぶことは非常に価値があります。

ここでは、NSErrorを使用してさまざまなシナリオでのエラーハンドリングを行うための実用的なコード例を紹介します。

○サンプルコード4:条件に応じて特定のNSErrorをスローする

Swiftの関数やメソッドは、条件に応じて特定のエラーをスローすることができます。

このコードでは、整数の引数が0未満の場合、特定のNSErrorをスローする関数を作成しています。

import Foundation

func checkNumber(_ num: Int) throws {
    if num < 0 {
        let error = NSError(domain: "com.example",
                            code: 1001,
                            userInfo: [NSLocalizedDescriptionKey: "引数が0未満です。"])
        throw error
    }
    print("引数は正常です。")
}

do {
    try checkNumber(-5)
} catch let error as NSError {
    print("エラーが発生しました: \(error.localizedDescription)")
}

このコードではcheckNumberという関数内で、引数が0未満の場合にNSErrorをスローしています。

実際に0未満の値を引数として関数を呼び出すと、「エラーが発生しました: 引数が0未満です。」というメッセージが出力されます。

○サンプルコード5:NSErrorを使った入力チェックの例

NSErrorは、ユーザーからの入力を検証する際にも有用です。

下記のコードは、ユーザーからの入力文字列が指定の文字数を超えている場合にNSErrorをスローする関数を表しています。

import Foundation

func validateInput(_ input: String, maxLength: Int) throws {
    if input.count > maxLength {
        let error = NSError(domain: "com.example",
                            code: 1002,
                            userInfo: [NSLocalizedDescriptionKey: "入力文字数が許容値を超えています。"])
        throw error
    }
    print("入力は正常です。")
}

do {
    try validateInput("Swiftプログラミング", maxLength: 10)
} catch let error as NSError {
    print("エラーが発生しました: \(error.localizedDescription)")
}

この例では、入力文字列がmaxLengthを超える場合にNSErrorをスローします。

実行すると、「エラーが発生しました: 入力文字数が許容値を超えています。」と出力されます。

○サンプルコード6:NSErrorと合わせての非同期処理のエラーハンドリング

非同期処理でのエラーハンドリングも、NSErrorを活用することで効果的に行うことができます。

ここでは、非同期処理中に発生したエラーをNSErrorとして取り扱う例を表しています。

import Foundation

func fetchData(completion: @escaping (String?, NSError?) -> Void) {
    DispatchQueue.global().async {
        // デモのため、ここではエラーを模擬しています。
        let error = NSError(domain: "com.example",
                            code: 1003,
                            userInfo: [NSLocalizedDescriptionKey: "データの取得に失敗しました。"])
        completion(nil, error)
    }
}

fetchData { (data, error) in
    if let err = error {
        print("エラーが発生しました: \(err.localizedDescription)")
    } else {
        print("データを取得しました: \(data ?? "No Data")")
    }
}

このコードでは、fetchData関数は非同期にデータを取得し、成功または失敗の結果をコールバックとして返します。

今回の例ではエラーを模擬しており、「エラーが発生しました: データの取得に失敗しました。」というメッセージが出力されます。

●NSErrorのカスタマイズ

NSErrorの強力な点の一つは、そのカスタマイズの柔軟性にあります。

標準のNSErrorをそのまま使用することもできますが、独自のエラーハンドリングのニーズに合わせてカスタマイズすることで、エラーハンドリングのプロセスをさらに洗練させることができます。

ここでは、NSErrorのカスタマイズ方法について3つのサンプルコードを通じて詳しく解説していきます。

○サンプルコード7:カスタムNSErrorサブクラスの作成

エラーの種類ごとに詳細な情報を付加したい場合、NSErrorのサブクラスを作成するという方法が考えられます。

ここでは、特定のエラー情報を持つカスタムNSErrorのサブクラスを作成する方法を表しています。

import Foundation

class CustomError: NSError {
    var customDescription: String?

    init(domain: String, code: Int, customDescription: String?) {
        self.customDescription = customDescription
        super.init(domain: domain, code: code, userInfo: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var localizedDescription: String {
        return customDescription ?? super.localizedDescription
    }
}

let error = CustomError(domain: "com.example", code: 2001, customDescription: "これはカスタムNSErrorの例です。")
print(error.localizedDescription)

このコードでは、NSErrorのサブクラスとしてCustomErrorを作成し、customDescriptionという新しいプロパティを追加しています。

このカスタムプロパティを使って独自のエラーメッセージを設定することができます。

「これはカスタムNSErrorの例です。」というメッセージが出力されることを確認できます。

○サンプルコード8:NSErrorに関連するカスタムデータの追加方法

NSErrorは、関連する追加情報をuserInfo辞書に保存することができます。

下記のコードは、NSErrorに関連するカスタムデータを追加する方法を表しています。

import Foundation

let userInfo: [String: Any] = ["RelatedID": 12345, "Timestamp": Date()]
let error = NSError(domain: "com.example", code: 2002, userInfo: userInfo)

if let relatedID = error.userInfo?["RelatedID"] as? Int {
    print("関連ID: \(relatedID)")
}

このコードでは、NSErrorのuserInfo辞書にRelatedIDTimestampというキーで情報を保存しています。

エラーが発生した際に、これらのカスタムデータを利用して追加の情報を取得することができます。「関連ID: 12345」という情報が出力されることを確認できます。

○サンプルコード9:NSErrorのローカリゼーションのカスタマイズ方法

NSErrorのエラーメッセージはローカリゼーションに対応しています。

しかし、場合によっては独自のローカリゼーション方法を実装したい場合もあるでしょう。

下記のコードは、NSErrorのローカリゼーション方法をカスタマイズする例を表しています。

import Foundation

let error = NSError(domain: "com.example", code: 2003, userInfo: [NSLocalizedDescriptionKey: "Error occurred."])

let locale = Locale(identifier: "ja_JP")
if let description = error.localizedDescription as? String {
    switch locale.identifier {
    case "ja_JP":
        print("エラーが発生しました。")
    default:
        print(description)
    }
}

このコードでは、NSLocalizedDescriptionKeyを使用してエラーメッセージをセットしています。

さらに、ロケールに応じてエラーメッセージの表示を切り替える処理を追加しています。

日本のロケールの場合、「エラーが発生しました。」というメッセージが出力されます。

●注意点と対処法

SwiftでNSErrorを使用する際には、多くの利点が享受できますが、同時にいくつかの注意点やミスが生じる可能性があります。

これらのミスを未然に防ぐためには、NSErrorの特性やSwiftとの相互作用を正確に理解しておくことが必要です。

ここでは、SwiftとNSErrorの間に存在する一般的な互換性の課題やよくあるミスについて、対処法とともに詳細に解説します。

○SwiftとNSErrorの互換性の課題

SwiftのError型とObjective-CのNSErrorとの間には、いくつかの互換性の課題が存在します。

これは、Swiftが独自のエラーハンドリングの仕組みを持っているためです。

このコードでは、SwiftのError型をNSErrorに変換する方法を表しています。

enum CustomSwiftError: Error {
    case sampleError
}

let swiftError: CustomSwiftError = .sampleError
let nsError = swiftError as NSError

print(nsError.domain) 
print(nsError.code)

SwiftのError型のインスタンスをNSErrorにキャストすることで変換できます。

上記のコードでは、SwiftのカスタムエラーCustomSwiftErrorをNSErrorに変換し、そのドメインとコードを出力しています。

変換に成功すれば、それぞれのドメインとコードがコンソールに出力されます。

○よくあるNSErrorの取り扱いのミスとその対処法

NSErrorの取り扱いにおいては、次のような一般的なミスが考えられます。

  1. NSErrorのインスタンスを正しく生成しない。
  2. NSErrorのプロパティを誤って参照する。

これらのミスの一例と対処法を紹介します。

import Foundation

// ミス: NSErrorをnilで初期化してしまう
var error: NSError? = nil
let fileData = try? Data(contentsOf: URL(string: "https://example.com")!, options: .alwaysMapped, error: &error)

// 対処法: nilの代わりに具体的なNSErrorを生成する
if fileData == nil {
    error = NSError(domain: "com.example", code: 2004, userInfo: [NSLocalizedDescriptionKey: "ファイルの読み込みに失敗しました。"])
    print(error?.localizedDescription ?? "不明なエラー")
}

このコードでは、Dataのインスタンスを生成する際にエラーが発生した場合、NSErrorをnilで初期化するミスを犯しています。

その後、正しくNSErrorを生成する対処法を示しています。対処法を用いると、「ファイルの読み込みに失敗しました。」というエラーメッセージがコンソールに出力されます。

まとめ

SwiftでのNSErrorの取り扱いは、アプリケーションの安定性やユーザーエクスペリエンス向上のために極めて重要なスキルとなります。

本ガイドでは、SwiftとNSErrorの基本的な関連性から、NSErrorの作成、活用例、カスタマイズ方法、注意点、対処法に至るまでを詳細に解説しました。

初心者から上級者まで、NSErrorの生成、取り扱い、カスタマイズの方法を9つのサンプルコードを用いて具体的に学ぶことができました。

各サンプルコードを実際に試すことで、NSErrorの機能や活用の幅をより深く理解することができるでしょう。

特にNSErrorのカスタマイズや、Swiftとの互換性に関連する課題は、多くの開発者が直面する問題であり、これらの課題を的確に理解し、適切な対処法を身につけることは非常に価値があります。

最後に、エラーハンドリングはコーディングの一部としてだけでなく、ユーザーにとっても安心感を提供するための大切な要素であることを忘れずに、適切なエラー処理の実装を心掛けることが求められます。

本ガイドが、SwiftでのNSErrorの取り扱いに関する理解を深める手助けとなり、より質の高いアプリケーション開発に役立つことを願っています。