iPhone 핵심 데이터 "생산"오류 처리


84

Core Data 오류를 처리하는 방법에 대한 Apple 참조에서 제공하는 예제 코드를 보았습니다. 즉 :

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();
}

그러나 결코 당신이 방법의 예를 해야 그것을 구현합니다.

누구든지 위의 방법을 설명하는 실제 "생산"코드를 가지고 있거나 지시 할 수 있습니까?

미리 감사드립니다, Matt


7
+1 이것은 훌륭한 질문입니다.
Dave DeLong

답변:


32

프로덕션 코드는 애플리케이션과 오류가 발생한 위치에 100 % 의존하기 때문에 아무도 프로덕션 코드를 보여주지 않습니다.

개인적으로 저는 99.9 %의 시간 동안이 오류가 개발 중에 발생하고 수정하면 프로덕션에서 볼 가능성 거의 없기 때문에 assert 문을 넣었습니다 .

어설 션 후 사용자에게 경고를 표시하고 복구 할 수없는 오류가 발생했으며 응용 프로그램이 종료 될 것임을 알립니다. 개발자에게 연락하도록 요청하는 문구를 넣어서이 작업을 추적 할 수 있습니다.

그 후에 앱을 "크래시"하고 나중에 문제를 추적하는 데 사용할 수있는 스택 추적을 생성하므로 거기에 abort ()를 그대로 둡니다.


Marcus-로컬 sqlite 데이터베이스 또는 XML 파일과 통신하는 경우 어설 션이 괜찮지 만 영구 저장소가 클라우드 기반 인 경우 더 강력한 오류 처리 메커니즘이 필요합니다.
dar512

4
iOS Core Data 영구 저장소가 클라우드 기반 인 경우 더 큰 문제가 있습니다.
Marcus S. Zarra 2013

3
나는 여러 주제에 대해 Apple의 의견에 동의하지 않습니다. 그것은 교육 상황 (Apple)과 참호 (나)의 차이입니다. 학업 상황에서 중단을 제거해야합니다. 실제로는 상상도 못했던 상황을 포착하는 데 유용합니다. Apple 문서 작성자는 모든 상황이 책임있는 척하는 것을 좋아합니다. 99.999 %가 그렇습니다. 정말로 예상치 못한 일을 위해 무엇을합니까? 무슨 일이 일어 났는지 알 수 있도록 충돌하고 로그를 생성합니다. 그것이 중단을위한 것입니다.
Marcus S. Zarra

1
@cschuff, 이들 중 어느 것도 핵심 데이터 -save:호출에 영향을 미치지 않습니다 . 이러한 모든 조건은 코드가이 지점에 도달하기 훨씬 전에 발생합니다.
Marcus S. Zarra

3
이는 저장하기 전에 포착하여 수정할 수있는 예상 오류입니다. Core Data에 데이터가 유효한지 물어보고 수정할 수 있습니다. 또한 사용할 때 유효한 모든 필드가 있는지 테스트 할 수 있습니다. 이는를 -save:호출 하기 오래 전에 처리 할 수있는 개발자 수준 오류입니다 .
Marcus S. Zarra 2014 년

32

이것은 iPhone에서 유효성 검사 오류를 처리하고 표시하기 위해 고안 한 일반적인 방법 중 하나입니다. 그러나 Marcus의 말이 옳습니다. 메시지를 사용자 친화적으로 수정하고 싶을 것입니다. 그러나 이것은 적어도 어떤 필드가 검증되지 않은 이유와 그 이유를 볼 수있는 시작점을 제공합니다.

- (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
확실히이 코드에서 잘못된 것을 볼 수 없습니다. 견고 해 보입니다. 개인적으로 저는 어설 션으로 Core Data 오류를 처리하는 것을 선호합니다. 나는 아직 생산에 들어가는 것을 보지 못했기 때문에 잠재적 생산 오류가 아닌 개발 오류로 항상 생각했습니다. 이 : 다른 보호 수준은 분명하지만
마커스 S. Zarra

2
Marcus, 주장에 대해 : 유효성 검사 측면에서 코드를 DRY로 유지하는 것에 대한 귀하의 의견은 무엇입니까? 제 생각에는 모델 (속한 위치)에서 유효성 검사 기준을 한 번만 정의하는 것이 매우 바람직합니다. . 즉 해야 사용자에게 적절한 MSG를 표시하는 데 필요한 모든 정보를합니다. MOC를 저장하기 전에 코드에서 이러한 검사를 다시 수행하는 것은 나에게 잘 어울리지 않습니다. 어떻게 생각해?
Johannes Fahrenkrug

2
이 댓글은 내 대답이 아니기 때문에 본 적이 없습니다. 모델에 유효성 검사를 넣은 경우에도 개체가 유효성 검사를 통과했는지 확인하고이를 사용자에게 제공해야합니다. 설계에 따라 필드 수준 (이 암호는 잘못됨 등) 또는 저장 지점에있을 수 있습니다. 디자이너의 선택. 나는 앱의 그 부분을 일반화하지 않을 것입니다.
Marcus S. Zarra

1
@ MarcusS.Zarra 나는 당신을 올바르게 @-멘션하지 않았기 때문에 당신이 그것을 얻지 못했다고 생각합니다 :) 나는 우리가 완전히 동의한다고 생각합니다 : 나는 유효성 검사 정보 가 모델에 있기를 원하지만 유효성 검사 를 트리거 할 때 결정 하고 유효성 검사 결과를 처리하고 표시하는 방법은 일반적이지 않아야하며 애플리케이션 코드의 적절한 위치에서 처리해야합니다.
Johannes Fahrenkrug 2013 년

코드가 멋져 보입니다. 내 유일한 질문은 경고를 표시하거나 분석을 로깅 한 후 Core Data 컨텍스트를 롤백하거나 앱을 중단해야한다는 것입니다. 그렇지 않으면 저장하지 않은 변경 사항으로 인해 다시 저장하려고 할 때 동일한 문제가 계속 발생한다고 생각합니다.
Jake

6

여기 아무도 실제로 처리하려는 방식으로 오류를 처리하지 않는다는 것에 놀랐습니다. 문서를 보면 알 수 있습니다.

여기에 오류가 발생하는 일반적인 이유는 다음과 같습니다. * 장치 공간이 부족합니다. * 장치가 잠겨있을 때 권한 또는 데이터 보호로 인해 영구 저장소에 액세스 할 수 없습니다. * 스토어를 현재 모델 버전으로 마이그레이션 할 수 없습니다. * 상위 디렉토리가 존재하지 않거나 생성 할 수 없거나 쓰기를 허용하지 않습니다.

따라서 핵심 데이터 스택을 설정할 때 오류를 발견하면 UIWindow의 rootViewController를 교체하고 사용자에게 장치가 꽉 찼거나이 앱이 작동하기에는 보안 설정이 너무 높음을 명확하게 알려주는 UI를 표시합니다. 또한 핵심 데이터 스택을 다시 시도하기 전에 문제 해결을 시도 할 수 있도록 '다시 시도'버튼을 제공합니다.

예를 들어 사용자는 저장 공간을 확보하고 내 앱으로 돌아가서 다시 시도 버튼을 누를 수 있습니다.

주장? 정말? 방에 개발자가 너무 많습니다!

또한 이러한 이유로 인해 저장 작업이 실패 할 수있는 방법을 언급하지 않는 온라인 자습서의 수에 놀랐습니다. 따라서이 분 동안 기기가 앱 저장 저장 저장으로 가득 차게 되었기 때문에 앱의 모든 저장 이벤트가 실패 할 수 있는지 확인해야합니다.


이 질문은 코어 데이터 스택의 저장에 관한 것이지 코어 데이터 스택 설정에 관한 것이 아닙니다. 하지만 제목이 오해의 소지가 있고 수정해야 할 수도 있다는 데 동의합니다.
valeCocoa

@valeCocoa에 동의하지 않습니다. 이 게시물은 프로덕션에서 저장 오류를 처리하는 방법에 대한 것입니다. 다시 한번보세요.

@roddanash 내가 말한 것입니다 ... WtH! :) 답변을 다시 살펴보십시오.
valeCocoa

넌 미쳤어 형

컨텍스트를 저장하는 동안 발생하는 오류에 관한 질문에 영구 저장소를 인스턴스화하는 동안 발생할 수있는 오류에 대한 문서의 일부를 붙여 넣습니다. 내가 미친 사람입니까? 확인…
valeCocoa

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

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.