私はSwiftについてあまり読みませんでしたが、気付いたことの1つは例外がないということです。では、Swiftでエラー処理はどのように行われるのでしょうか。誰かがエラー処理に関連するものを見つけましたか?
Swift 2では、新しいエラー処理メカニズムがあるため、状況が少し変わりました。これは、例外とやや似ていますが詳細が異なります。
関数/メソッドがエラーをスローする可能性があることを示したい場合は、このようにthrows
キーワードを含める必要があります。
func summonDefaultDragon() throws -> Dragon
注:関数が実際にスローできるエラーの種類に関する指定はありません。この宣言は、関数がErrorTypeを実装する任意の型のインスタンスをスローできること、またはまったくスローしていないことを示しています
関数を呼び出すには、このようにtryキーワードを使う必要があります。
try summonDefaultDragon()
この行は通常、次のようにdo-catchブロックとして存在します。
do {
let dragon = try summonDefaultDragon()
} catch DragonError.dragonIsMissing {
// Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
// Other specific-case error-handlng
} catch {
// Catch all error-handling
}
注:catch節はSwiftパターンマッチングの強力な機能をすべて使用しているので、ここでは非常に柔軟です。
自分自身がthrows
キーワードでマークされている関数からスロー関数を呼び出している場合は、エラーを伝播させることにします。
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
あるいは、try?
を使ってthrowing関数を呼び出すこともできます。
let dragonOrNil = try? summonDefaultDragon()
このようにして、何らかのエラーが発生した場合は、戻り値またはnilを取得します。このようにしてエラーオブジェクトを取得することはありません。
つまり、try?
を以下のような便利なステートメントと組み合わせることもできます。
if let dragon = try? summonDefaultDragon()
または
guard let dragon = try? summonDefaultDragon() else { ... }
最後に、エラーが実際には発生しないことを知っていることを確認し(たとえば、既に確認済みであることが前提条件であるため)、try!
キーワードを使用します。
let dragon = try! summonDefaultDragon()
関数が実際にエラーをスローした場合は、アプリケーションにランタイムエラーが発生し、アプリケーションは終了します。
エラーをスローするには、このようにthrowキーワードを使用します。
throw DragonError.dragonIsMissing
ErrorType
プロトコルに準拠したものなら何でも投げることができます。初心者のためにNSError
はこのプロトコルに準拠していますが、おそらく次のように追加のデータの断片で、複数の関連するエラーをグループ化することを可能にするenumベースのErrorType
で行きたいと思うでしょう
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
新しいSwift 2および3のエラーメカニズムとJava/C#/ C++スタイルの例外との主な違いは次のとおりです。
do-catch
+ try
+ defer
と従来のtry-catch-finally
構文。do-catch
ブロックはNSExceptionをキャッチしないでしょう、そしてその逆も同様です、あなたがObjCを使わなければならないということです。NSError
(関数を返すfalse
)またはBool
(関数を返すnil
)を返し、エラーの詳細をAnyObject
を渡すというCocoaのNSErrorPointer
メソッド規約と互換性があります。エラー処理を容易にするための追加の合成糖として、さらに2つの概念があります。
defer
キーワードを使用)guard
キーワードを使用して)guardステートメントを使用すると、if/elseコードを通常のエラーチェック/シグナリングコードよりも少し少なく書くことができます。ランタイムエラー:
Leandrosがランタイムエラー(ネットワーク接続の問題、データの解析、ファイルを開くなど)の処理について提案しているように、ObjCの場合と同様にNSError
を使用する必要があります。これは、Foundation、AppKit、UIKitなどがエラーを報告するためです。それは言語のことよりもフレームワークのことです。
使用されているもう一つの頻繁なパターンはAFNetworkingのようにセパレータの成功/失敗ブロックです:
var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
success: { (NSURLSessionDataTask) -> Void in
println("Success")
},
failure:{ (NSURLSessionDataTask, NSError) -> Void in
println("Failure")
})
それでも失敗ブロックはエラーを説明するNSError
インスタンスを頻繁に受け取りました。
プログラマーのエラー:
プログラマのエラー(配列要素の範囲外アクセス、関数呼び出しに渡された無効な引数など)に対しては、ObjCで例外を使用しました。 Swift言語は例外をサポートしていないようです(throw
、catch
などのキーワード)。ただし、ドキュメントに示されているように、ObjCと同じランタイムで実行されているため、まだNSExceptions
をスローすることができます。
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
ObjCコードで例外をキャッチすることを選択することはできますが、純粋なSwiftでそれらをキャッチすることはできません。
問題は、プログラマーのエラーに例外をスローするのか、それともAppleが言語ガイドで示唆しているようにアサーションを使用するのかということです。
更新2015年6月9日 - 非常に重要
Swift 2.0にはtry
、throw
、およびcatch
のキーワードが付属していますが、最も魅力的なものは次のとおりです。
Swiftは、エラーを生成するObjective-Cのメソッドを、Swiftのネイティブエラー処理機能に従ってエラーをスローするメソッドに自動的に変換します。
注意:デリゲートメソッドやNSErrorオブジェクト引数を持つ完了ハンドラを受け取るメソッドなど、エラーを発生させるメソッドは、Swiftによってインポートされたときにスローされるメソッドにはなりません。
抜粋:Apple Inc.「CocoaとObjective-CでSwiftを使用する(Swift 2 Prerelease)」iBooks。
例:(本から)
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
NSLog(@"Error: %@", error.domain);
}
Swiftと同等のものは次のようになります。
let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
print ("Error: \(error.domain)")
}
エラーを投げる:
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]
自動的に呼び出し元に伝えられます。
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
Appleの書籍、The Swift Programming Languageから、エラーはenumを使って処理する必要があるようです。
これは本からの例です。
enum ServerResponse {
case Result(String, String)
case Error(String)
}
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")
switch success {
case let .Result(sunrise, sunset):
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
let serverResponse = "Failure... \(error)"
}
アップル社の「The Swift Programming Language」。 https://itun.es/br/jEUH0.l
更新
Appleのニュースブック、「CocoaとObjective-CでSwiftを使う」から。 Swift言語を使用してランタイム例外が発生することはないので、try-catchがないのはそのためです。代わりに Optional Chaining を使用してください。
これは本からの一続きです:
たとえば、以下のコードリストでは、lengthプロパティとcharacterAtIndex:メソッドがNSDateオブジェクトに存在しないため、1行目と2行目は実行されません。 myLength定数はオプションのIntであると推論され、nilに設定されます。 3行目に示すように、if – letステートメントを使用して、オブジェクトが応答しない可能性があるメソッドの結果を条件付きでラップ解除することもできます。
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}
抜粋:Apple Inc.「CocoaとObjective-CでSwiftを使う」iBooks。 https://itun.es/br/1u3-0.l
そして本はまたObjective-C(NSError Object)からのココアエラーパターンを使うことを勧めます。
Swiftでのエラー報告は、Objective-Cと同じパターンに従いますが、オプションの戻り値を提供するという追加の利点もあります。最も単純な場合は、関数からBool値を返して、成功したかどうかを示します。エラーの理由を報告する必要がある場合は、関数にNSErrorPointer型のNSError outパラメータを追加できます。このタイプは、Objective-CのNSError **とほぼ同等ですが、メモリの安全性がさらに高まり、オプションの型指定も可能になります。以下のコードリストに示すように、接頭辞&演算子を使用して、オプションのNSError型への参照をNSErrorPointerオブジェクトとして渡すことができます。
var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
encoding: NSUTF8StringEncoding,
error: &writeError)
if !written {
if let error = writeError {
println("write failure: \(error.localizedDescription)")
}
}
抜粋:Apple Inc.「CocoaとObjective-CでSwiftを使う」iBooks。 https://itun.es/br/1u3-0.l
Objective-Cのアプローチと同様に、Swiftには例外はありません。
開発段階では、assert
を使用して、発生する可能性があるエラーを検出できます。本番に進む前に修正が必要です。
古典的なNSError
のアプローチは変更されていません。あなたはNSErrorPointer
を送ります。
簡単な例:
var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
println("An error occurred \(error)")
} else {
println("Contents: \(contents)")
}
推奨される「Swift Way」は次のとおりです。
func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}
var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
println("write failure 1: \(writeError!.localizedDescription)")
// assert(false) // Terminate program
}
しかし、エラー処理を最後に別のブロックに移動するので、追いかけやすいので、try/catchを使用することをお勧めします。この配置は「ゴールデンパス」と呼ばれることもあります。幸運にもあなたはクロージャでこれをすることができます:
TryBool {
write("~/Error2")(error: $0) // The code to try
}.catch {
println("write failure 2: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
再試行機能を追加するのも簡単です。
TryBool {
write("~/Error3")(error: $0) // The code to try
}.retry {
println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
return write("~/Error3r") // The code to retry
}.catch {
println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
TryBoolのリストは次のとおりです。
class TryBool {
typealias Tryee = NSErrorPointer -> Bool
typealias Catchee = NSError? -> ()
typealias Retryee = (NSError?, UInt) -> Tryee
private var tryee: Tryee
private var retries: UInt = 0
private var retryee: Retryee?
init(tryee: Tryee) {
self.tryee = tryee
}
func retry(retries: UInt, retryee: Retryee) -> Self {
self.retries = retries
self.retryee = retryee
return self
}
func retry(retryee: Retryee) -> Self {
return self.retry(1, retryee)
}
func retry(retries: UInt) -> Self {
// For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
self.retries = retries
retryee = nil
return self
}
func retry() -> Self {
return retry(1)
}
func catch(catchee: Catchee) {
var error: NSError?
for numRetries in 0...retries { // First try is retry 0
error = nil
let result = tryee(&error)
if result {
return
} else if numRetries != retries {
if let r = retryee {
tryee = r(error, numRetries)
}
}
}
catchee(error)
}
}
Bool値の代わりにOptionalの戻り値をテストするための同様のクラスを書くことができます。
class TryOptional<T> {
typealias Tryee = NSErrorPointer -> T?
typealias Catchee = NSError? -> T
typealias Retryee = (NSError?, UInt) -> Tryee
private var tryee: Tryee
private var retries: UInt = 0
private var retryee: Retryee?
init(tryee: Tryee) {
self.tryee = tryee
}
func retry(retries: UInt, retryee: Retryee) -> Self {
self.retries = retries
self.retryee = retryee
return self
}
func retry(retryee: Retryee) -> Self {
return retry(1, retryee)
}
func retry(retries: UInt) -> Self {
// For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
self.retries = retries
retryee = nil
return self
}
func retry() -> Self {
return retry(1)
}
func catch(catchee: Catchee) -> T {
var error: NSError?
for numRetries in 0...retries {
error = nil
let result = tryee(&error)
if let r = result {
return r
} else if numRetries != retries {
if let r = retryee {
tryee = r(error, numRetries)
}
}
}
return catchee(error)
}
}
TryOptionalバージョンでは、以降のプログラミングを容易にする、Optional以外の戻り型を強制しています。 'スウィフトウェイ:
struct FailableInitializer {
init?(_ id: Int, error: NSErrorPointer) {
// Always fails in example
if error != nil {
error.memory = NSError(domain: "", code: id, userInfo: [:])
}
return nil
}
private init() {
// Empty in example
}
static let fallback = FailableInitializer()
}
func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
return FailableInitializer(id, error: error)
}
var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
println("failableInitializer failure code: \(failError!.code)")
failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap
TryOptionalを使う:
let failure2 = TryOptional {
failableInitializer(2)(error: $0)
}.catch {
println("failableInitializer failure code: \($0!.code)")
return FailableInitializer.fallback
}
let failure3 = TryOptional {
failableInitializer(3)(error: $0)
}.retry {
println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
return failableInitializer(31)
}.catch {
println("failableInitializer failure code: \($0!.code)")
return FailableInitializer.fallback
}
自動展開に注意してください。
編集:この答えはうまくいきますが、それはObjective-Cの音訳にすぎませんスイフトに。 Swift 2.0での変更により廃止されました。上記のGuilherme Torres Castroの答えは、Swiftでのエラー処理の推奨方法の非常に優れた入門書です。 VOS
それを理解するには少し時間がかかりましたが、私はそれを疑ったと思います。それは醜いようです。 Objective-Cバージョンを超える薄いスキン以上のものはありません。
NSErrorパラメータを使って関数を呼び出す...
var fooError : NSError ? = nil
let someObject = foo(aParam, error:&fooError)
// Check something was returned and look for an error if it wasn't.
if !someObject {
if let error = fooError {
// Handle error
NSLog("This happened: \(error.localizedDescription)")
}
} else {
// Handle success
}`
エラーパラメータをとる関数を書く...
func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {
// Do stuff...
if somethingBadHasHappened {
if error {
error.memory = NSError(domain: domain, code: code, userInfo: [:])
}
return nil
}
// Do more stuff...
}
Try Catch機能を提供するObjective Cの基本ラッパー。 https://github.com/williamFalcon/SwiftTryCatch
のように使用します。
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
これはSwift 2.0への更新の答えです。私は、Javaのような機能豊富なエラー処理モデルを楽しみにしています。最後に、彼らは良い知らせを発表しました。 ここ
エラー処理モデル:Swift 2.0の新しいエラー処理モデルは、おなじみのtry、throw、およびcatchのキーワードで即座に自然になります。何よりも、それはApple SDKとNSErrorと完璧に連携するように設計されています。実際、NSErrorはSwiftのErrorTypeに準拠しています。それについての詳細を聞くためにあなたは間違いなくSwiftの新機能に関するWWDCセッションを見たいと思うでしょう。
例:
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
Guilherme Torres Castroが述べたように、Swift 2.0では、try
name__、catch
name__、do
name__をプログラミングで使用できます。
たとえば、CoreDataでは、&error
をパラメータとしてmanagedContext.executeFetchRequest(fetchRequest, error: &error)
に入れる代わりに、データメソッドを取得します。ここではmanagedContext.executeFetchRequest(fetchRequest)
を使用し、try
name__、catch
name__( )でエラーを処理するだけです。 )
do {
let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if let results = fetchedResults{
people = results
}
} catch {
print("Could not fetch")
}
Xcode7 Betaをすでにダウンロードしている場合Documentations and API Referenceでthrowing errorsを検索して、最初に表示される結果を選択すると、この新しい構文に対して何ができるかという基本的な考え方がわかります。しかし、完全なドキュメントはまだ多くのAPIに投稿されていません。
より手の込んだエラー処理のテクニックは、にあります。
Swiftの新機能(2015セッション106 28分30秒)
エラー処理はSwift 2.0の新機能です。 try
、throw
およびcatch
キーワードを使用します。
公式Apple Swiftブログの Apple Swift 2.0アナウンスを参照してください。
Swift 2から始めて、他の人がすでに述べたように、エラー処理はdo/try/catchとErrorType enumの使用を通して最もよく達成されます。これは同期メソッドでは非常にうまく機能しますが、非同期エラー処理には少し巧妙さが必要です。
この記事には、この問題に対する優れたアプローチがあります。
https://jeremywsherman.com/blog/2015/06/17/using-Swift-throws-with-completion-callbacks/
要約する:
// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData
// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
{
completionHandler()
}
それから、上記のメソッドの呼び出しは次のようになります。
self.loadData("someString",
completionHandler:
{ result: LoadDataResult in
do
{
let data = try result()
// success - go ahead and work with the data
}
catch
{
// failure - look at the error code and handle accordingly
}
})
これは、非同期関数に個別のerrorHandlerコールバックを渡すよりも少しきれいに見えます。これは、Swift 2より前ではこれがどのように処理されるかでした。
私が見たことは、デバイスの性質上、ユーザーに大量の不可解なエラー処理メッセージを投げたくないということです。ほとんどの関数がオプションの値を返すのはそのためで、オプションを無視するようにコードを書くだけです。関数が失敗したことを意味するnilに戻らない場合は、メッセージなどをポップすることができます。
例外を処理するための素晴らしく単純なlib: TryCatchFinally-Swift
他の人と同じように、Objective Cの例外機能をラップしています。
このように使用してください。
try {
println(" try")
}.catch { e in
println(" catch")
}.finally {
println(" finally")
}