7

NSError **多くの Cocoa メソッドは、エラーを報告するために使用するオプションの引数を取ります。エラーが発生する唯一の方法が、予期しないランタイム条件ではなく、自分の側のプログラミング エラーによるものである状況でも、このようなメソッドを使用していることに気付くことがよくあります。そのため、ユーザーに表示されることを行うエラー処理コードを書きたくありません。エラーが発生したときに本当にやりたいことは、コードをできるだけ簡潔で読みやすいものに保ちながら、エラーをログに記録する (そしておそらくクラッシュする) ことだけです。

問題は、「コードを簡潔に保つ」という目的と「エラーをログに記録する」という目的が互いに緊張関係にあることです。私は頻繁に次の 2 つのアプローチのどちらかを選択しますが、どちらも嫌いです。

1. エラー ポインタ引数に NULL を渡します。

[managedObjectContext save:NULL];
  • 利点: 簡潔で読みやすく、エラーが予期されていないことを明確に示します。ここでのエラーは論理的にありえないという私の信念が正しかった限り、まったく問題ありません。
  • 短所: 失敗してエラー発生した場合、ログに記録されず、デバッグが難しくなります。場合によっては、エラーが発生したことに気付かないこともあります。

2.NSError **毎回同じ定型コードで、 を渡し、結果のエラーをログに記録します。

NSError *error;
[managedObjectContext save:&error];
if (error) {
    NSLog(@"Error while saving: %@", error);
}
  • 利点: エラーは黙って渡されません。警告が表示され、デバッグ情報が提供されます。
  • 短所:恐ろしく冗長です。書くのも読むのも遅く、インデントのいくつかのレベル内にすでにネストされていると、コードが読みにくくなるように感じます。エラーをログに記録するためだけにこれを日常的に実行し、読み取り中に定型文をスキップすることに慣れると、実行時に発生すると予想されるエラーに対して、実際に読み取り中のコードに重大なエラー処理ブロックがあることに気付かないことがあります。

Python、Java、PHP、Javascript などの言語のバックグラウンドを持っているので、慣れ親しんだ言語ではエラーの種類を通知するために定型文を 4 行も余分に書かなければならないのは面倒だと思います。明示的にエラーをチェックするコードを記述する必要なく、例外または警告を介して調べることができます。

私が理想的に望むのは、すべてのメソッド呼び出しでボイラープレートを記述する必要なく、これらのメソッドによって作成されたエラーを自動的にログに記録するために使用できる狡猾なハックです。これにより、怠惰な NULL 渡しアプローチとエラーの両方の利点が得られます。 -logging ボイラープレート。つまり、次のように書きたいと思います。

[managedObjectContext save:&magicAutologgingError];

メソッドが を作成した場合、NSErrorどういうわけか、魔法のようにログに記録されることを知っています。

どうすればいいのかよくわかりません。NSError自分自身をログに記録するサブクラスを使用することを検討しましたdeallocが、Cocoa のメソッドが作成するエラー オブジェクトをインスタンス化する責任がないため、サブクラスはいずれにしても使用されないことに気付きました。メソッド スウィズリングを使用して、すべて NSErrorの s がこのようにログオンできるdeallocようにすることを検討しましたが、それが実際に望ましいかどうかはわかりません。ログに記録したいポインターに使用できるメモリ内の特定の一定のスペースを監視するある種のオブザーバークラスを使用することNSErrorを考えましたが、私が知る限り、メモリ内の任意のスペースを監視するためにKVOのようなことをする方法はありません、そのため、ログに記録するエラーを繰り返しチェックするスレッドを持つ以外に、これを実装するためのアプローチがわかりません。

誰でもこれを達成する方法を見ることができますか?

4

4 に答える 4

1

1 つのアプローチは、NSError **パラメーターを受け取るブロックを定義し、そのようなブロック内にエラーを生成する式を配置し (ブロック パラメーターをエラー パラメーターとしてコードに渡す)、その型のブロックを実行する関数を記述し、エラー参照をログに記録します。例えば:

// Definitions of block type and function
typedef void(^ErrorLoggingBlock)(NSError **errorReference);

void ExecuteErrorLoggingBlock(ErrorLoggingBlock block)
{
    NSError *error = nil;
    block(&error);
    if (error) {
        NSLog(@"error = %@", error);
    }
}

...

// Usage:
__block NSData *data1 = nil;
ErrorLoggingBlock block1 = ^(NSError **errorReference) {
    data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://www.google.com"] options:0 error:errorReference];
};
__block NSData *data2 = nil;
ErrorLoggingBlock block2 = ^(NSError **errorReference) {
    data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://wwwwwlskjdlsdkjk.dsldksjl.sll"] options:0 error:errorReference];
};

ExecuteErrorLoggingBlock(block1);
ExecuteErrorLoggingBlock(block2);

NSLog(@"data1 = %@", data1);
NSLog(@"data2 = %@", data2);

それでも冗長すぎる場合は、プリプロセッサ マクロまたは Xcode コード スニペットのいずれかを検討してください。次の一連のマクロは、かなり回復力があることがわかりました。

#define LazyErrorConcatenatePaste(a,b) a##b
#define LazyErrorConcatenate(a,b) LazyErrorConcatenatePaste(a,b)
#define LazyErrorName LazyErrorConcatenate(lazyError,__LINE__)
#define LazyErrorLogExpression(expr) NSError *LazyErrorName; expr; if (LazyErrorName) { NSLog(@"error: %@", LazyErrorName);}

使い方はこんな感じでした。

LazyErrorLogExpression(NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://google.com"]
                                                             options:0
                                                               error:&LazyErrorName])
NSLog(@"data1 = %@", data1);
LazyErrorLogExpression(NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://sdlkdslkdsslk.alskdj"]
                                                             options:0
                                                               error:&LazyErrorName])
NSLog(@"data2 = %@", data2);

マクロはエラー変数名を一意にし、メソッド呼び出しの改行に耐性がありました。ソース ファイルに余分なコード行を一切入れないことに固執している場合は、これが最も安全なアプローチかもしれません。少なくとも、異常な例外をスローしたり、フレームワーク メソッドをスウィズリングしたりする必要はありません。アシスタント エディターのコード。

ただし、Objective-C の冗長性は意図的なものであり、特にコードの読みやすさとメンテナンスのしやすさを向上させるためのものであり、より良い解決策はカスタム Xcode スニペットを作成することです。上記のマクロを使用するほど速くは書きませんが (ただし、キーボード ショートカットとオートコンプリートを使用すると、それでも非常に速くなります)、将来の読者にとっては完全に明らかです。次のテキストをスニペット ライブラリにドラッグして、補完ショートカットを定義できます。

NSError *<#errorName#>;
<#expression_writing_back_error#>;
if (<#errorName#>) {
    NSLog(@"error: %@", <#errorName#>);
}

最後の免責事項: これらのパターンはログ記録にのみ使用する必要があり、戻り値はエラー回復の場合の成功または失敗を実際に決定する必要があります。ただし、一般的なエラー回復コードが必要な場合は、成功または失敗を示すブール値を返すようにブロック ベースのアプローチをかなり簡単に作成できます。

于 2013-09-28T18:54:13.187 に答える
1

必要な処理を行うラッパー関数 (またはカテゴリ メソッド) を作成するだけです。

bool MONSaveManagedObjectContext(NSManagedObjectContext * pContext) {
 NSError * error = nil;
 bool result = [pContext save:&error];
 if (!result && nil != error) {
  // handle  the error how you like -- may be different in debug/release
  NSLog(@"Error while saving: %@", error);
 }
 return result;
}

代わりにそれを呼び出します。または、エラー処理を別にしたいかもしれません:

void MONCheckError(NSError * pError, NSString * pMessage) {
 if (nil != pError) {
  // handle  the error how you like -- may be different in debug/release
  NSLog(@"%@: %@", pMessage, pError);
 }
}

...

NSError * outError = nil;
bool result = [managedObjectContext save:&outError];
MONCheckError(outError, @"Error while saving");

常に重複コードに注意してください:)


メソッド スウィズリングを使用して、すべての NSErrors がこのように dealloc でログに記録されるようにすることを検討しましたが、それが実際に望ましいかどうかはわかりません。

それは望ましくありません。

于 2013-09-28T17:38:22.230 に答える