LoginSignup
3
1

More than 5 years have passed since last update.

Swift の Runtime Exception はキャッチできるのかと試したこと

Last updated at Posted at 2019-01-19

はじめに

Swift の Runtime Exception はキャッチできるのかと試した話

追記

  • 2019/01/27 ライブラリ公開

やりたいこと

Swiftの例外キャッチは throw を実装したメソッドに対して,行える.

do{
    try hogehoge()
}catch{
    ...
}

しかし,ランタイム例外は do-catch で囲ってもキャッチできない.

do{
    let temps = [0, 1, 2]
    print("\(temps[10])") // クラッシュ
    // try temps[10] // Xcodeに怒られる
}catch{
    ...
}

上記の単純な配列indexぐらいなら事前に確認すれば問題ない.しかしながら,いくつもの処理が複雑で絡み合うシーンに出くわしたとき,サードライブラリなどで手出しができないとき,事前確認が複雑なときにそれらが起こす例外をキャッチしたいときがある.

Objective-Cはこれらの例外が取れていた.それなら,Swift内で例外をキャッチしたい処理をObjective-Cで行えばできるかなとやってみた.

本記事のコードは mitsuharu/demo_swift_catches_exceptions で公開してます.

処理をブロック化して例外をキャッチする

Objective-Cで,処理をブロックで渡して,例外が起こったときのコールバックを行うクラス ExcBlock を用意した.

ExcBlock.h
typedef void (^Block)(void);
typedef void (^Completion)( NSException *exception );

@interface ExcBlock : NSObject

+ (void)executeBlock:(Block)block completion:(Completion)completion;

@end
ExcBlock.m
@implementation ExcBlock

+ (void)executeBlock:(Block)block completion:(Completion)completion
{
    @try {
        NSLog(@"execute block");
        block();
    } @catch (NSException *exception) {
        NSLog(@"exception = %@", exception);
        completion(exception);
    } @finally {
    }
}

@end

Swift で呼び出して,故意に例外を起こして確認した.しかしながら,ブロック内の例外がキャッチされることはなく,そのままクラッシュした.

ExcBlock.execute({
            let temps = [0, 1, 2]
            print("\(temps[10])")
            // ブロックの中で起こる例外がキャッチできないまま落ちる
        }) { (exception) in
            print(exception)
        }

この手法を調べると同様なのを見かけるので,以前のバージョンでは動いていたのかもしれない.Swiftの初期バージョン時はあまりにも破壊的アップデートが多かったので,Objective-Cに戻って開発して,Swiftの情報をあまりキャッチアップしてこなかったので,あまり詳しくない.

個別に例外をキャッチする

汎用的にSwiftのコードをブロックで渡したからキャッチできなかったと考えた.処理を丸々Objective-Cに渡してみたらどうだろうか.クラスを上書きして,例外が起こる箇所の専用メソッドを作成する.仮に UITableView の reloadRows の例外をキャッチする ExcTableView を用意した.

ExcTableView.h
typedef void (^ExcCompletion)( NSException *exception );

@interface ExcTableView : UITableView

- (void)exc_reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
                  withRowAnimation:(UITableViewRowAnimation)animation
                     excCompletion:(ExcCompletion)excCompletion;

@end
ExcTableView.m
@implementation ExcTableView

- (void)exc_reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
                  withRowAnimation:(UITableViewRowAnimation)animation
                         excCompletion:(ExcCompletion)excCompletion{

    @try {
        [self reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];
    } @catch (NSException *exception) {
        excCompletion(exception);
    } @finally {
    }    
}

@end

Swiftでは故意に例外が起こるようにようにしたところ,アプリはクラッシュせずに例外を取得できた.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        let ip = IndexPath(row: 100, section: 100)

        self.tableView.exc_reloadRows(at: [ip], with: UITableView.RowAnimation.automatic) { (exception) in
            print("[exc_reloadRows] exception:\(exception)")
            // 無事に "[exc_reloadRows] exception:attempt to delete row 100 from section 100, but there are only 1 sections before the update
" が標準出力に表示された
        }
    }

ライブラリを公開しました(追記, 2019/01/27)

UITableViewのreloadRowsあたりの例外をキャッチする ライブラリ を公開しました.上記のようなUITableViewを継承したクラスではなく,Extensionで生やして使いやすいようにしてます.例外周りなので使用時は注意してもらえれば.

ライブラリの使用例
let indexPath = IndexPath(row: 100, section: 100)
self.tableView.exc.reloadRows(at: [indexPath], with: .automatic) { (exception) in
    print("exception:\(exception)")
}   

まとめ

例外をキャッチしたい処理をObjective-Cに置き換えれば,Swiftでも例外をキャッチすることができた.しかしながら,個別に準備を用意しなければならないので現実的ではないです.それに,それ実質Objective-Cなのでは?とやってて思った.

3
1
1

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
3
1