iPhoneコアデータの「本番」エラー処理


84

Appleが提供するサンプルコードで、CoreDataエラーの処理方法について説明しています。すなわち:

NSError *error = nil;
if (![context save:&error]) {
/*
 Replace this implementation with code to handle the error appropriately.

 abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
 */
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}

しかし、それをどのように実装すべきかの例は決してありません。

上記の方法を説明する実際の「本番」コードを持っている人はいますか(またはその方向を示すことができますか)。

よろしくお願いします、マット


7
+1これは素晴らしい質問です。
Dave DeLong 2010

回答:


32

アプリケーションとエラーが発生する場所に100%依存するため、誰も製品コードを表示しません。

個人的には、このエラーが開発中に発生する可能性が99.9%あり、そこで修正すると本番環境で発生する可能性が非常に低いため、assertステートメントをそこに配置します。

アサート後、ユーザーにアラートを表示し、回復不能なエラーが発生したことと、アプリケーションが終了することをユーザーに知らせます。また、開発者に連絡するように宣伝文句を入れて、これが完了したことを追跡できるようにすることもできます。

その後、abort()をそのままにして、アプリを「クラッシュ」させ、後で問題を追跡するために使用できるスタックトレースを生成します。


マーカス-ローカルのsqliteデータベースまたはXMLファイルと通信している場合はアサーションは問題ありませんが、永続ストアがクラウドベースの場合はより堅牢なエラー処理メカニズムが必要です。
dar512 2013

4
iOS Core Data永続ストアがクラウドベースの場合、より大きな問題が発生します。
マーカスS.ザラ2013

3
私は多くのトピックについてAppleに同意しません。それは、教育状況(Apple)と塹壕(私)の違いです。学術的な状況から、はい、中絶を削除する必要があります。実際には、想像もしていなかった状況を捉えるのに役立ちます。Appleのドキュメント作成者は、あらゆる状況に責任があるふりをするのが好きです。それらの99.999%はそうです。本当に思いがけないことに対してどうしますか?クラッシュしてログを生成し、何が起こったのかを知ることができます。それが中絶の目的です。
マーカスS.ザラ

1
@cschuff、これらはいずれもコアデータ-save:呼び出しに影響を与えません。これらの条件はすべて、コードがこのポイントに到達するずっと前に発生します。
マーカスS.ザラ2014

3
これは、保存前にキャッチして修正できる予想されるエラーです。データが有効かどうかをCoreDataに問い合わせて、修正することができます。さらに、消費時にそれをテストして、すべての有効なフィールドが存在することを確認できます。これは開発者レベルのエラーであり、-save:が呼び出されるずっと前に処理できます。
マーカスS.ザラ2014

32

これは、iPhoneで検証エラーを処理して表示するために私が思いついた一般的な方法の1つです。しかし、マーカスは正しいです。おそらく、メッセージを微調整して、よりユーザーフレンドリーにしたいと思うでしょう。ただし、これにより、少なくとも、検証されなかったフィールドとその理由を確認するための開始点が得られます。

- (void)displayValidationError:(NSError *)anError {
    if (anError && [[anError domain] isEqualToString:@"NSCocoaErrorDomain"]) {
        NSArray *errors = nil;

        // multiple errors?
        if ([anError code] == NSValidationMultipleErrorsError) {
            errors = [[anError userInfo] objectForKey:NSDetailedErrorsKey];
        } else {
            errors = [NSArray arrayWithObject:anError];
        }

        if (errors && [errors count] > 0) {
            NSString *messages = @"Reason(s):\n";

            for (NSError * error in errors) {
                NSString *entityName = [[[[error userInfo] objectForKey:@"NSValidationErrorObject"] entity] name];
                NSString *attributeName = [[error userInfo] objectForKey:@"NSValidationErrorKey"];
                NSString *msg;
                switch ([error code]) {
                    case NSManagedObjectValidationError:
                        msg = @"Generic validation error.";
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        msg = [NSString stringWithFormat:@"The attribute '%@' mustn't be empty.", attributeName];
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:  
                        msg = [NSString stringWithFormat:@"The relationship '%@' doesn't have enough entries.", attributeName];
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        msg = [NSString stringWithFormat:@"The relationship '%@' has too many entries.", attributeName];
                        break;
                    case NSValidationRelationshipDeniedDeleteError:
                        msg = [NSString stringWithFormat:@"To delete, the relationship '%@' must be empty.", attributeName];
                        break;
                    case NSValidationNumberTooLargeError:                 
                        msg = [NSString stringWithFormat:@"The number of the attribute '%@' is too large.", attributeName];
                        break;
                    case NSValidationNumberTooSmallError:                 
                        msg = [NSString stringWithFormat:@"The number of the attribute '%@' is too small.", attributeName];
                        break;
                    case NSValidationDateTooLateError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is too late.", attributeName];
                        break;
                    case NSValidationDateTooSoonError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is too soon.", attributeName];
                        break;
                    case NSValidationInvalidDateError:                    
                        msg = [NSString stringWithFormat:@"The date of the attribute '%@' is invalid.", attributeName];
                        break;
                    case NSValidationStringTooLongError:      
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' is too long.", attributeName];
                        break;
                    case NSValidationStringTooShortError:                 
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' is too short.", attributeName];
                        break;
                    case NSValidationStringPatternMatchingError:          
                        msg = [NSString stringWithFormat:@"The text of the attribute '%@' doesn't match the required pattern.", attributeName];
                        break;
                    default:
                        msg = [NSString stringWithFormat:@"Unknown error (code %i).", [error code]];
                        break;
                }

                messages = [messages stringByAppendingFormat:@"%@%@%@\n", (entityName?:@""),(entityName?@": ":@""),msg];
            }
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Validation Error" 
                                                            message:messages
                                                           delegate:nil 
                                                  cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
            [alert release];
        }
    }
}

楽しい。


3
確かに、このコードには何の問題もありません。しっかりしているように見えます。個人的には、コアデータエラーをアサーションで処理することを好みます。私はまだ本番環境に移行するのを見たことがないので、潜在的な本番環境エラーではなく、開発エラーであると常に考えてきました。これは確かに別のレベルの保護ですが:)
Marcus S. Zarra 2010

2
マーカス、アサーションについて:検証の観点からコードをDRYに保つことについてどう思いますか?私の意見では、モデル(それが属する場所)で検証基準を一度だけ定義することが非常に望ましいです:このフィールドは空にすることはできず、そのフィールドは少なくとも5文字の長さである必要があり、そのフィールドはこの正規表現と一致する必要があります。それはする必要があり、ユーザーに適切なMSGを表示するために必要なすべての情報も。MOCを保存する前に、コードでこれらのチェックを再度実行することは、どういうわけか私にはうまくいきません。どう思いますか?
Johannes Fahrenkrug 2010

2
私の答えにはなかったので、このコメントを見たことがありません。モデルに検証を入れた場合でも、オブジェクトが検証に合格したかどうかを確認し、それをユーザーに提示する必要があります。フィールドレベル(このパスワードが間違っているなど)または保存ポイントにある可能性のある設計によって異なります。デザイナーの選択。私はアプリのその部分を一般的なものにしません。
マーカスS.ザラ2013年

1
@ MarcusS.Zarra @-メンションを正しく行わなかったため、取得できなかったと思います:)完全に同意すると思います:検証情報をモデルに含めたいのですが、検証をトリガーするタイミングと検証結果の処理方法と表示方法は一般的なものではなく、アプリケーションコードの適切な場所で処理する必要があります。
Johannes Fahrenkrug 2013年

コードは素晴らしく見えます。私の唯一の質問は、アラートを表示した後、または分析をログに記録した後、Core Dataコンテキストをロールバックするか、アプリを中止する必要があるかということです。それ以外の場合は、保存しない変更を再度保存しようとすると、同じ問題が発生し続けると思います。
ジェイク2013年

6

ここで誰も実際にエラーを本来の方法で処理していないことに驚いています。ドキュメントを見ると、わかります。

ここでのエラーの一般的な理由は次のとおりです。*デバイスの容量が不足しています。*デバイスがロックされている場合の権限またはデータ保護のため、永続ストアにアクセスできません。*ストアを現在のモデルバージョンに移行できませんでした。*親ディレクトリが存在しないか、作成できないか、書き込みが許可されていません。

そのため、コアデータスタックの設定中にエラーを見つけた場合は、UIWindowのrootViewControllerを交換して、デバイスがいっぱいであるか、セキュリティ設定が高すぎてこのアプリが機能しないことをユーザーに明確に伝えるUIを表示します。また、コアデータスタックが再試行される前に問題の修正を試みることができるように、「再試行」ボタンを提供します。

たとえば、ユーザーはストレージスペースを解放し、アプリに戻って[再試行]ボタンを押すことができます。

主張しますか?本当に?部屋に開発者が多すぎます!

また、これらの理由で保存操作が失敗する可能性について言及していないオンラインチュートリアルの数にも驚いています。そのため、デバイスがアプリの保存でいっぱいになったため、アプリのどこでも保存イベントが失敗する可能性があることを確認する必要があります。


この質問は、Core Dataスタックへの保存に関するものであり、CoreDataスタックのセットアップに関するものではありません。しかし、私はそのタイトルが誤解を招く可能性があり、おそらくそれを変更する必要があることに同意します。
valeCocoa 2017

@valeCocoaに同意しません。投稿は明らかに、本番環境での保存エラーの処理方法に関するものです。もう一度見てください。

@roddanashこれは私が言ったことです…WtH!:)あなたの答えをもう一度見てください。
valeCocoa 2017

あなたはクレイジーな仲間です

コンテキストの保存中に発生するエラーに関する質問に永続ストアをインスタンス化するときに発生する可能性のあるエラーのドキュメントの一部を貼り付けますが、私はクレイジーですか?
わかりました

5

この一般的な保存機能がはるかに優れた解決策であることがわかりました。

- (BOOL)saveContext {
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        DDLogError(@"[%@::%@] Whoops, couldn't save managed object context due to errors. Rolling back. Error: %@\n\n", NSStringFromClass([self class]), NSStringFromSelector(_cmd), error);
        [self.managedObjectContext rollback];
        return NO;
    }
    return YES;
}

保存が失敗すると、NSManagedObjectContextがロールバックされます。つまり、最後の保存以降にコンテキストで実行されたすべての変更がリセットされます。したがって、上記の保存機能を使用して変更を常に保持するように注意して、できるだけ早く定期的にデータを失う可能性があるため、注意する必要があります。

データを挿入する場合、これはより緩いバリアントであり、他の変更を存続させることができます。

- (BOOL)saveContext {
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        DDLogError(@"[%@::%@] Whoops, couldn't save. Removing erroneous object from context. Error: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), object.objectId, error);
        [self.managedObjectContext deleteObject:object];
        return NO;
    }
    return YES;
}

注:ここでのロギングにはCocoaLumberjackを使用しています。

これを改善する方法についてのコメントは大歓迎です!

BRクリス


:私はこれを達成するために、ロールバックを使用しようとすると、私は奇妙な行動を取得していstackoverflow.com/questions/34426719/...
malhal

現在、代わりに元に戻すを使用しています
malhal 2016

2

私は@JohannesFahrenkrugの有用な答えのSwiftバージョンを作成しました。これは有用です:

public func displayValidationError(anError:NSError?) -> String {
    if anError != nil && anError!.domain.compare("NSCocoaErrorDomain") == .OrderedSame {
        var messages:String = "Reason(s):\n"
        var errors = [AnyObject]()
        if (anError!.code == NSValidationMultipleErrorsError) {
            errors = anError!.userInfo[NSDetailedErrorsKey] as! [AnyObject]
        } else {
            errors = [AnyObject]()
            errors.append(anError!)
        }
        if (errors.count > 0) {
            for error in errors {
                if (error as? NSError)!.userInfo.keys.contains("conflictList") {
                    messages =  messages.stringByAppendingString("Generic merge conflict. see details : \(error)")
                }
                else
                {
                    let entityName = "\(((error as? NSError)!.userInfo["NSValidationErrorObject"] as! NSManagedObject).entity.name)"
                    let attributeName = "\((error as? NSError)!.userInfo["NSValidationErrorKey"])"
                    var msg = ""
                    switch (error.code) {
                    case NSManagedObjectValidationError:
                        msg = "Generic validation error.";
                        break;
                    case NSValidationMissingMandatoryPropertyError:
                        msg = String(format:"The attribute '%@' mustn't be empty.", attributeName)
                        break;
                    case NSValidationRelationshipLacksMinimumCountError:
                        msg = String(format:"The relationship '%@' doesn't have enough entries.", attributeName)
                        break;
                    case NSValidationRelationshipExceedsMaximumCountError:
                        msg = String(format:"The relationship '%@' has too many entries.", attributeName)
                        break;
                    case NSValidationRelationshipDeniedDeleteError:
                        msg = String(format:"To delete, the relationship '%@' must be empty.", attributeName)
                        break;
                    case NSValidationNumberTooLargeError:
                        msg = String(format:"The number of the attribute '%@' is too large.", attributeName)
                        break;
                    case NSValidationNumberTooSmallError:
                        msg = String(format:"The number of the attribute '%@' is too small.", attributeName)
                        break;
                    case NSValidationDateTooLateError:
                        msg = String(format:"The date of the attribute '%@' is too late.", attributeName)
                        break;
                    case NSValidationDateTooSoonError:
                        msg = String(format:"The date of the attribute '%@' is too soon.", attributeName)
                        break;
                    case NSValidationInvalidDateError:
                        msg = String(format:"The date of the attribute '%@' is invalid.", attributeName)
                        break;
                    case NSValidationStringTooLongError:
                        msg = String(format:"The text of the attribute '%@' is too long.", attributeName)
                        break;
                    case NSValidationStringTooShortError:
                        msg = String(format:"The text of the attribute '%@' is too short.", attributeName)
                        break;
                    case NSValidationStringPatternMatchingError:
                        msg = String(format:"The text of the attribute '%@' doesn't match the required pattern.", attributeName)
                        break;
                    default:
                        msg = String(format:"Unknown error (code %i).", error.code) as String
                        break;
                    }

                    messages = messages.stringByAppendingString("\(entityName).\(attributeName):\(msg)\n")
                }
            }
        }
        return messages
    }
    return "no error"
}`
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.