Objective-C / Cocoaで例外をスローする


回答:


528

私は[NSException raise:format:]次のように使用します:

[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];

9
この方法は、@throw([NSException exceptionWith…])より簡潔であるので、このアプローチよりも優先されます。
Sam Soffes

9
害からの重要な注意事項(必ずお読みくださいstackoverflow.com/questions/324284/324805#324805
e.James

26
私も一般的にこれを好みますが、1つの落とし穴があります。Xcodeの現在のバージョンである可能性がありますが、[NSException raise ...]構文は、値を返すメソッドからの出口パスとしてパーサーに認識されていないようです。この構文を使用すると「コントロールがvoid以外の関数の終わりに到達する可能性があります」という警告が表示されますが、@ throw([NSException exceptionWith…])構文では、パーサーはそれを出口として認識し、警告を表示しません。
mattorb 2011

1
@mpstx私はあなたが与えた理由のために常にthrow構文を使用しています(これは2年後のXcode 4.6でもまだ関連しており、おそらく常にそうです)。警告を回避したい場合は、例外のスローが関数の出口点であることをIDEに認識させることが重要です。
マークアメリー2013

FWIW私は、@ try / @ catchブロックも「コントロールがvoid以外の関数の終わりに到達した」という警告の偽陰性(つまり、警告が表示されるはずのタイミングで表示されない)を
引き起こすことに気づいてい

256

ここに注意の言葉があります。Objective-Cでは、多くの類似言語とは異なり、通常の操作で発生する可能性のある一般的なエラー状況で例外を使用しないようにする必要があります。

AppleのObj-C 2.0に関するドキュメントには次のように記載されています。「重要:Objective-Cでは例外がリソースを大量に消費します。一般的なフロー制御や、単にエラー(ファイルにアクセスできないなど)を示すために例外を使用しないでください。」

Appleの概念的な例外処理ドキュメントは同じことを説明していますが、「重要:範囲外のコレクションアクセス、不変オブジェクトの変更の試み、無効なメッセージの送信など、プログラミングまたは予期しないランタイムエラーの例外の使用を予約する必要があります。 、およびウィンドウサーバーへの接続が失われます。通常、アプリケーションが実行時ではなく作成されるときに、例外を伴うこれらの種類のエラーに対処します。[.....]例外の代わりに、エラーオブジェクト(NSError)とCocoaアプリケーションで予期されるエラーを通知するには、Cocoaエラー配信メカニズムが推奨される方法です。」

これの理由の一部は、Objective-Cのプログラミングイディオム(単純なケースでは戻り値を使用し、より複雑なケースでは参照パラメーター(多くの場合NSErrorクラス)を使用する)に準拠することです。最後に(そして最も重要なことに)Objective-Cの例外は、Cのsetjmp()関数とlongjmp()関数の薄いラッパーであり、基本的には慎重なメモリ処理を台無しにしています。この説明を参照してください。


11
これはほとんどのプログラミング言語に当てはまると思います:「一般的なエラー状況で例外を使用しないようにしてください」。同じことがJavaにも当てはまります。例外のある(たとえば)ユーザー入力エラーを処理することは悪い習慣です。リソースの使用だけでなく、コードを明確にするためにも。
beetstra

6
さらに重要なことに、Cocoaの例外は、回復不可能なプログラムエラーを示すように設計されています。そうでなければ、フレームワークに対して実行され、未定義の動作につながる可能性があります。詳細については、stackoverflow.com / questions / 3378696 / iphone-try-end-try /…をご覧ください。
KPM

9
「Javaでも同じことが言えます。」同意しない。通常のエラー状態では、Javaでチェック例外を問題なく使用できます。もちろん、ランタイム例外は使用しません。
ダニエルライアン

私はCocoaの方法を好みます(例外はプログラマーエラーのみを対象とする)ので、Javaでも同じように行いたいのですが、実際には、環境での一般的な方法を使用する必要があり、エラー処理の例外はObjective-Cでは悪臭がしますが、Javaではその目的でよく使用されます。
gnasher729 2014年

1
このコメントは質問に返答しません。おそらく、OPはアプリをクラッシュさせて、クラッシュレポートフレームワークが期待どおりに機能しているかどうかをテストしたいだけかもしれません。
Simon

62
@throw([NSException exceptionWith…])

Xcodeは、@throwステートメントと同様に、ステートメントを関数の出口点として認識しますreturn@throw構文を使用すると、「コントロールがvoid以外の関数の終わりに到達する可能性があります」という誤った警告が表示されなくなります[NSException raise:…]

また、@throwNSExceptionクラスではないオブジェクトをスローするために使用することもできます。


11
@Steph Thirion:詳細については、developer.apple.com / documentation / Cocoa / Conceptual / Exceptions /…を参照てください。ボトムライン?どちらも機能しますが、@ throwを使用して、NSExceptionクラスではないオブジェクトをスローできます。
e.James、2008年

33

について[NSException raise:format:]。Javaのバックグラウンドから来た人にとって、JavaはExceptionとRuntimeExceptionを区別することを思い出してください。Exceptionはチェックされた例外で、RuntimeExceptionはチェックされていません。特に、Javaは「通常のエラー状態」にはチェック済み例外を使用し、「プログラマーエラーが原因のランタイムエラー」にはチェックなし例外を使用することを推奨しています。Objective-Cの例外は、チェックされていない例外を使用するのと同じ場所で使用する必要があるようです。チェックされた例外を使用する場所では、エラーコードの戻り値またはNSError値が推奨されます。


1
はい、これは正しいです(ただし、4年後:D)、NSErrorクラスから拡張した独自のエラークラスABCErrorを作成し、それをNSExceptionsではなくチェック例外に使用します。プログラマエラー(数値形式の問題などの予期しない状況)が発生した場合は、NSExceptionを発生させます。
チャトゥラム2013年

15

NSExceptionを拡張する独自のクラスで@throwを使用する方が一貫していると思います。次に、同じ表記をtry catchに使用します。

@try {
.....
}
@catch{
...
}
@finally{
...
}

Appleが例外をスローして処理する方法をここで説明: キャッチ例外 例外をスローします


tryブロックで実行時例外によってまだクラッシュしました
famfamfam

14

ObjC 2.0以降、Objective-C例外はCのsetjmp()longjmp()のラッパーではなくなり、C ++例外と互換性があります。@ tryは「無料」ですが、例外のスローとキャッチははるかにコストがかかります。

とにかく、アサーション(NSAssertとNSCAssertマクロファミリーを使用)はNSExceptionをスローし、Ries状態としてそれらを使用することは正気です。


知っておきたい!変更したくないサードパーティライブラリがあり、最小のエラーでも例外がスローされます。アプリ内の1か所でそれらをキャッチする必要があるため、不快に感じるだけですが、これにより少し気分が良くなります。
Yuri Brigance 2014

8

NSErrorを使用して、例外ではなく失敗を伝えます。

NSErrorに関する要点:

  • NSErrorを使用すると、Cスタイルのエラーコード(整数)が根本原因を明確に特定し、エラーハンドラーがエラーを解決できるようになります。SQLiteのようなCライブラリからのエラーコードをNSErrorインスタンスで非常に簡単にラップできます。

  • NSErrorにはオブジェクトであるという利点もあり、userInfoディクショナリメンバーでエラーをより詳細に説明する方法を提供します。

  • しかし何よりも、NSErrorをスローすることはできないので、ホットポテトを単純にさらにスローしてコールスタックをさらに上げる他の言語とは対照的に、エラー処理へのより積極的なアプローチが奨励されます。意味のある方法で処理されない(OOPの情報隠蔽の最大の信条に従うことを信じている場合はそうではありません)。

参照リンク: 参照


このコメントは質問に返答しません。おそらく、OPはアプリをクラッシュさせて、クラッシュレポートフレームワークが期待どおりに機能しているかどうかをテストしたいだけかもしれません。
Simon

7

これが「ビッグ・ナードランチガイド(第4版)」から学んだ方法です。

@throw [NSException exceptionWithName:@"Something is not right exception"
                               reason:@"Can't perform this operation because of this or that"
                             userInfo:nil];

わかりましたが、についてはあまりわかりませんuserInfo:nil。:)
・クール

6

2つの方法を使用して、try catchブロックで例外を発生させることができます

@throw[NSException exceptionWithName];

または2番目の方法

NSException e;
[e raise];

3

通常のプログラムフローを制御するために例外を使用しないでください。ただし、ある値が目的の値と一致しない場合は常に例外をスローする必要があります。

たとえば、ある関数が値を受け入れ、その値がnilになることが決して許可されない場合、「スマート」なことをしようとするのではなく、例外をスローしても問題ありません...

リース


0

プログラミングエラーを示す状況にあり、アプリケーションの実行を停止したい場合にのみ、例外をスローする必要があります。したがって、例外をスローする最善の方法は、NSAssertおよびNSParameterAssertマクロを使用し、NS_BLOCK_ASSERTIONSが定義されていないことを確認することです。


0

ケースのサンプルコード:@throw([NSException exceptionWithName:...

- (void)parseError:(NSError *)error
       completionBlock:(void (^)(NSString *error))completionBlock {


    NSString *resultString = [NSString new];

    @try {

    NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];

    if(!errorData.bytes) {

        @throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
    }


    NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
                                                                 options:NSJSONReadingAllowFragments
                                                                   error:&error];

    resultString = dictFromData[@"someKey"];
    ...


} @catch (NSException *exception) {

      NSLog( @"Caught Exception Name: %@", exception.name);
      NSLog( @"Caught Exception Reason: %@", exception.reason );

    resultString = exception.reason;

} @finally {

    completionBlock(resultString);
}

}

使用:

[self parseError:error completionBlock:^(NSString *error) {
            NSLog(@"%@", error);
        }];

さらに高度なユースケース:

- (void)parseError:(NSError *)error completionBlock:(void (^)(NSString *error))completionBlock {

NSString *resultString = [NSString new];

NSException* customNilException = [NSException exceptionWithName:@"NilException"
                                                          reason:@"object is nil"
                                                        userInfo:nil];

NSException* customNotNumberException = [NSException exceptionWithName:@"NotNumberException"
                                                                reason:@"object is not a NSNumber"
                                                              userInfo:nil];

@try {

    NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];

    if(!errorData.bytes) {

        @throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
    }


    NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
                                                                 options:NSJSONReadingAllowFragments
                                                                   error:&error];

    NSArray * array = dictFromData[@"someArrayKey"];

    for (NSInteger i=0; i < array.count; i++) {

        id resultString = array[i];

        if (![resultString isKindOfClass:NSNumber.class]) {

            [customNotNumberException raise]; // <====== HERE is just the same as: @throw customNotNumberException;

            break;

        } else if (!resultString){

            @throw customNilException;        // <======

            break;
        }

    }

} @catch (SomeCustomException * sce) {
    // most specific type
    // handle exception ce
    //...
} @catch (CustomException * ce) {
    // most specific type
    // handle exception ce
    //...
} @catch (NSException *exception) {
    // less specific type

    // do whatever recovery is necessary at his level
    //...
    // rethrow the exception so it's handled at a higher level

    @throw (SomeCustomException * customException);

} @finally {
    // perform tasks necessary whether exception occurred or not

}

}


-7

ビジネスルールの例外を示すためであっても、目的Cで例外を通常使用しない理由はありません。Appleは気にかけているNSErrorを使うと言うことができます。Obj Cは古くから存在しており、かつてC ++のすべてのドキュメントで同じことが言われていました。例外のスローとキャッチがどれほど高額であっても問題にならない理由は、例外の存続期間が非常に短く、通常のフローに対する例外です。私の人生で、例外がスローされてキャッチされるまでに長い時間がかかったと言う人は誰もいません。

また、目的のC自体が高すぎると考え、代わりにCまたはC ++でコーディングする人もいます。したがって、常にNSErrorを使用するというのは知識がなく、偏執的です。

しかし、このスレッドの質問にはまだ回答がありません。例外をスローするための最良の方法は何ですか。NSErrorを返す方法は明白です。

そうです:[NSException raise:... @throw [[NSException alloc] initWithName ....または@throw [[MyCustomException ...?

ここでは、チェック済み/未チェックのルールを上記とは少し異なります。

(ここではJavaメタファーを使用して)チェック済み/チェックなしの実際の違いは重要です->例外から回復できるかどうか。そして、回復とは、単にクラッシュしないことを意味します。

したがって、回復可能な例外には@throwを使用してカスタム例外クラスを使用します。これは、複数の@catchブロックで特定の種類のエラーを検索するアプリメソッドがあるためです。たとえば、アプリがATMマシンの場合、「WithdrawalRequestExceedsBalanceException」の@catchブロックがあります。

実行時の例外にはNSException:raiseを使用します。これは、より高いレベルでキャッチしてログに記録する以外に、例外から回復する方法がないためです。そして、そのためのカスタムクラスを作成しても意味がありません。

とにかくそれは私がやっていることですが、もっと良い、同じように表現力豊かな方法があれば、私も知りたいです。私のコードでは、ずっと前にCのコーディングをやめたので、APIからNSErrorを渡されても、NSErrorを返すことはありません。


4
「目的Cで例外を通常使用しない理由はない」などの一般化ステートメントを作成する前に、エラーケースの通常のフローの一部として例外を使用してサーバーをプログラムすることをお勧めします。信じられないかもしれませんが、ObjCで高性能アプリケーション(または少なくともアプリケーションの一部)を作成する理由があり、例外をスローすると、通常、パフォーマンスが著しく低下します。
jbenet

6
確かに、Cocoaで例外を使用しない非常に良い理由があります。詳細については、Bill Bumgarnerの回答をこちらでご覧ください:stackoverflow.com/questions/3378696/iphone-try-end-try/…。彼は自分が話していることを知っています(ヒント:雇用主を確認してください)。Cocoaの例外は回復不可能なエラーとして扱われ、システムが不安定な状態になる可能性があります。NSErrorは、一般的なエラーを渡す方法です。
ブラッド・ラーソン

例外は例外的です。ビジネスルールの失敗は確かに対象外です。「例外を多用するコードを見つけて設計することで、適切なパフォーマンスを得ることができます。」MSDN経由codinghorror.com/blog/2004/10/...
ジョナサンWatmough

3
ブロックから例外をスローすることはできません。ARC環境で例外がスローされると、プログラムがリークする可能性があります。したがって、反対票。
Moszi

「例外のスローとキャッチがどれほど高額であってもかまいません」パフォーマンスが重要なエミュレータを書いています。高価な例外をたくさん投げることはできません。
NobodyNada 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.