web-dev-qa-db-ja.com

NSExceptionをSwiftでキャッチ

Swiftの次のコードは、NSInvalidArgumentException例外を発生させます。

task = NSTask()
task.launchPath = "/SomeWrongPath"
task.launch()

どうすれば例外をキャッチできますか?私が理解するように、Swiftのtry/catchは、Swift内でスローされるエラー用であり、NSTask(ObjCで記述されていると思われます)のようなオブジェクトから発生するNSExceptions用ではありません。私はSwiftが初めてなので、明らかな何かを見逃しているかもしれません...

Edit:ここにバグのレーダーがあります(特にNSTaskの場合): openradar.appspot.com/22837476

62
silyevsk

NSExceptionsをSwift 2エラーに変換するコードを次に示します。

これで使用できます

do {
    try ObjC.catchException {

       /* calls that might throw an NSException */
    }
}
catch {
    print("An error ocurred: \(error)")
}

ObjC.h:

#import <Foundation/Foundation.h>

@interface ObjC : NSObject

+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error;

@end

ObjC.m

#import "ObjC.h"

@implementation ObjC 

+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error {
    @try {
        tryBlock();
        return YES;
    }
    @catch (NSException *exception) {
        *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
        return NO;
    }
}

@end

これを忘れずに「* -Bridging-Header.h」に追加してください。

#import "ObjC.h"
115
freytag

私が提案するのは、例外をキャッチして代わりにNSErrorを返すC関数を作成することです。そして、この関数を使用します。

関数は次のようになります。

NSError *tryCatch(void(^tryBlock)(), NSError *(^convertNSException)(NSException *))
{
    NSError *error = nil;
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        error = convertNSException(exception);
    }
    @finally {
        return error;
    }
}

そして、少し橋渡しの助けを借りて、あなただけを呼び出す必要があります:

if let error = tryCatch(task.launch, myConvertFunction) {
    print("An exception happened!", error.localizedDescription)
    // Do stuff
}
// Continue task

注:実際にはテストしていません。Objective-CとSwiftをプレイグラウンドで使用する簡単で簡単な方法は見つかりませんでした。

12
BPCorp

TL; DR:Carthageを使用して含めるhttps://github.com/eggheadgames/SwiftTryCatchまたは含めるCocoaPodshttps://github.com/ravero/SwiftTryCatch .

次に、アプリがクラッシュすることを恐れずに次のようなコードを使用できます。

import Foundation
import SwiftTryCatch

class SafeArchiver {

    class func unarchiveObjectWithFile(filename: String) -> AnyObject? {

        var data : AnyObject? = nil

        if NSFileManager.defaultManager().fileExistsAtPath(filename) {
            SwiftTryCatch.tryBlock({
                data = NSKeyedUnarchiver.unarchiveObjectWithFile(filename)
                }, catchBlock: { (error) in
                    Logger.logException("SafeArchiver.unarchiveObjectWithFile")
                }, finallyBlock: {
            })
        }
        return data
    }

    class func archiveRootObject(data: AnyObject, toFile : String) -> Bool {
        var result: Bool = false

        SwiftTryCatch.tryBlock({
            result =  NSKeyedArchiver.archiveRootObject(data, toFile: toFile)
            }, catchBlock: { (error) in
                Logger.logException("SafeArchiver.archiveRootObject")
            }, finallyBlock: {
        })
        return result
    }
}

@BPCorpが受け入れた答えは意図したとおりに機能しますが、発見したように、このObjective Cコードを多数決Swift=フレームワークに組み込み、テストを実行しようとすると、少し面白くなります。クラス関数が見つからないという問題(エラー:未解決の識別子の使用)。そのため、そのため、一般的な使いやすさのために、Carthageライブラリとしてパッケージ化しました。一般的な使用のため。

奇妙なことに、Swift + ObjCフレームワークを問題なく使用できます。苦労しているのはフレームワークの単体テストだけです。

PRがリクエストされました! (CocoaPodとCarthageのコンボビルドを作成し、いくつかのテストを行うことは素晴らしいことです)。

9
mm2001

コメントで述べたように、このAPIは、他の方法で回復可能な障害状態に対して例外をスローすることはバグです。 それをファイルし、NSErrorベースの代替を要求します。NSTaskは以前に遡るので、ほとんどの現在の状況は時代錯誤ですApple標準化例外はプログラマーのエラーのみに適用されます。

それまでの間、couldは他の回答のメカニズムの1つを使用してObjCで例外をキャッチし、Swiftに例外を渡しますが、そうしないことに注意してください非常に安全ではありません。 ObjC(およびC++)例外の背後にあるスタック巻き戻しメカニズムは脆弱であり、ARCと根本的に互換性がありません。これは、Appleがプログラマーエラーに対してのみ例外を使用する理由の一部です。開発中にアプリのすべての例外ケースを(理論的には)整理でき、例外がないという考えです。実動コードで発生します(一方、SwiftエラーまたはNSErrorsは、回復可能な状況エラーまたはユーザーエラーを示している可能性があります)。

より安全なソリューションは、APIを呼び出す前に、APIが例外をスローし、それらを処理する可能性のある状態を予測することです。 NSArrayにインデックスを付ける場合は、最初にcountを確認してください。 launchPathNSTaskを、存在しないか実行できない可能性のあるものに設定している場合は、タスクを起動する前にNSFileManagerを使用して確認してください。

3
rickster

documentation で指定されているように、これは簡単で軽量な方法です。

do {
    try fileManager.moveItem(at: fromURL, to: toURL)
} catch let error as NSError {
    print("Error: \(error.domain)")
}
0
Rémy Virin