Xử lý lỗi dữ liệu cốt lõi của iPhone "Sản xuất"


84

Tôi đã thấy trong mã ví dụ do Apple cung cấp tham chiếu đến cách bạn nên xử lý lỗi Dữ liệu cốt lõi. I E:

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

Nhưng không bao giờ có bất kỳ ví dụ nào về cách bạn nên thực hiện nó.

Có ai có (hoặc có thể chỉ cho tôi theo hướng của) một số mã "sản xuất" thực tế minh họa phương pháp trên.

Cảm ơn trước, Matt


7
+1 đây là một câu hỏi xuất sắc.
Dave DeLong 14/02/10

Câu trả lời:


32

Không ai sẽ cho bạn xem mã sản xuất bởi vì nó phụ thuộc 100% vào ứng dụng của bạn và lỗi xảy ra ở đâu.

Cá nhân tôi đưa ra một tuyên bố khẳng định ở đó bởi vì 99,9% thời gian lỗi này sẽ xảy ra trong quá trình phát triển và khi bạn sửa nó ở đó, rất khó khả năng bạn sẽ thấy nó trong sản xuất.

Sau khi khẳng định, tôi sẽ đưa ra một cảnh báo cho người dùng, cho họ biết đã xảy ra lỗi không thể khôi phục và ứng dụng sẽ thoát. Bạn cũng có thể đưa ra một lời giới thiệu ở đó yêu cầu họ liên hệ với nhà phát triển để hy vọng bạn có thể theo dõi quá trình này được thực hiện.

Sau đó, tôi sẽ để abort () ở đó vì nó sẽ "làm sập" ứng dụng và tạo ra một dấu vết ngăn xếp mà bạn hy vọng có thể sử dụng sau này để theo dõi vấn đề.


Marcus - Mặc dù các xác nhận là ổn nếu bạn đang nói chuyện với cơ sở dữ liệu sqlite cục bộ hoặc tệp XML, nhưng bạn cần một cơ chế xử lý lỗi mạnh mẽ hơn nếu kho lưu trữ liên tục của bạn dựa trên đám mây.
dar512

4
Nếu kho lưu trữ liên tục Dữ liệu cốt lõi iOS của bạn dựa trên đám mây, bạn sẽ gặp vấn đề lớn hơn.
Marcus S. Zarra

3
Tôi không đồng ý với Apple về một số chủ đề. Đó là sự khác biệt giữa tình huống giảng dạy (Apple) và trong chiến hào (tôi). Từ một tình huống học tập, có, bạn nên loại bỏ phá thai. Trên thực tế, chúng rất hữu ích để nắm bắt những tình huống mà bạn không bao giờ tưởng tượng được. Những người viết tài liệu về Apple thích giả vờ rằng mọi tình huống đều có trách nhiệm. 99,999% trong số đó là. Bạn làm gì cho điều thực sự bất ngờ? Tôi gặp sự cố và tạo nhật ký để có thể tìm hiểu điều gì đã xảy ra. Đó là những gì phá thai.
Marcus S. Zarra

1
@cschuff, không ai trong số đó ảnh hưởng đến -save:cuộc gọi dữ liệu cốt lõi . Tất cả những điều kiện đó xảy ra rất lâu trước khi mã của bạn đạt đến điểm này.
Marcus S. Zarra

3
Đó là một lỗi dự đoán có thể được phát hiện và sửa chữa trước khi lưu. Bạn có thể hỏi Core Data xem dữ liệu có hợp lệ không và sửa lại. Ngoài ra, bạn có thể kiểm tra điều đó tại thời điểm tiêu thụ để đảm bảo tất cả các trường hợp lệ đều có mặt. Đó là lỗi cấp nhà phát triển có thể được xử lý rất lâu trước khi -save:được gọi.
Marcus S. Zarra

32

Đây là một phương pháp chung mà tôi nghĩ ra để xử lý và hiển thị các lỗi xác thực trên iPhone. Nhưng Marcus nói đúng: Bạn có thể muốn điều chỉnh các tin nhắn để thân thiện hơn với người dùng. Nhưng điều này ít nhất cung cấp cho bạn một điểm bắt đầu để xem trường nào không xác thực và tại sao.

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

Thưởng thức.


3
Chắc chắn không thể thấy bất cứ điều gì sai với mã này. Trông chắc chắn. Cá nhân tôi thích xử lý các lỗi Dữ liệu cốt lõi bằng một xác nhận. Tôi vẫn chưa thấy một cái nào được đưa vào sản xuất nên tôi luôn coi chúng là lỗi phát triển hơn là lỗi sản xuất tiềm ẩn. Mặc dù đây chắc chắn là một cấp độ bảo vệ khác :)
Marcus S. Zarra

2
Marcus, về các xác nhận: Ý kiến ​​của bạn về việc giữ mã DRY trong điều kiện xác thực? Theo ý kiến ​​của tôi, rất mong muốn xác định tiêu chí xác thực của bạn chỉ một lần, trong mô hình (nơi nó thuộc về): Trường này không được để trống, trường đó phải dài ít nhất 5 ký tự và trường đó phải khớp với regex này . Đó phải là tất cả thông tin cần thiết để hiển thị một tin nhắn thích hợp cho người dùng. Bằng cách nào đó, tôi không thể thực hiện lại những kiểm tra đó trong mã trước khi lưu MOC. Bạn nghĩ sao?
Johannes Fahrenkrug

2
Chưa bao giờ thấy bình luận này vì nó không có trong câu trả lời của tôi. Ngay cả khi bạn đặt xác thực vào mô hình, bạn vẫn cần phải kiểm tra xem đối tượng có vượt qua xác thực hay không và trình bày điều đó cho người dùng. Tùy thuộc vào thiết kế có thể ở cấp trường (mật khẩu này không hợp lệ, v.v.) hoặc tại điểm lưu. Sự lựa chọn của nhà thiết kế. Tôi sẽ không làm cho phần đó của ứng dụng chung chung.
Marcus S. Zarra

1
@ MarcusS.Zarra Tôi đoán bạn không bao giờ hiểu được vì tôi đã không chính xác @ -mention you :) Tôi nghĩ chúng tôi hoàn toàn đồng ý: Tôi muốn xác thực- thông tin có trong mô hình, nhưng quyết định khi nào kích hoạt xác thực và cách xử lý và trình bày kết quả xác nhận không nên chung chung và cần được xử lý ở những vị trí thích hợp trong mã ứng dụng.
Johannes Fahrenkrug

Mã trông tuyệt vời. Câu hỏi duy nhất của tôi là, sau khi hiển thị cảnh báo hoặc ghi nhật ký phân tích, tôi nên khôi phục ngữ cảnh Dữ liệu cốt lõi hay hủy bỏ ứng dụng? Nếu không, tôi đoán rằng những thay đổi chưa được lưu sẽ tiếp tục gây ra sự cố tương tự khi bạn cố gắng lưu lại.
Jake

6

Tôi ngạc nhiên là không có ai ở đây thực sự xử lý lỗi theo cách nó được xử lý. Nếu bạn nhìn vào tài liệu, bạn sẽ thấy.

Các lý do điển hình gây ra lỗi ở đây bao gồm: * Thiết bị hết dung lượng. * Không thể truy cập kho lưu trữ liên tục, do quyền hoặc bảo vệ dữ liệu khi thiết bị bị khóa. * Không thể chuyển cửa hàng sang phiên bản kiểu máy hiện tại. * Thư mục mẹ không tồn tại, không thể tạo hoặc không cho phép ghi.

Vì vậy, nếu tôi tìm thấy lỗi khi thiết lập ngăn xếp dữ liệu cốt lõi, tôi sẽ hoán đổi rootViewController của UIWindow và hiển thị UI thông báo rõ ràng cho người dùng rằng thiết bị của họ có thể đã đầy hoặc cài đặt bảo mật của họ quá cao để Ứng dụng này hoạt động. Tôi cũng cung cấp cho họ nút 'thử lại' để họ có thể cố gắng khắc phục sự cố trước khi thử lại ngăn xếp dữ liệu cốt lõi.

Ví dụ: người dùng có thể giải phóng một số dung lượng lưu trữ, hãy quay lại Ứng dụng của tôi và nhấn nút thử lại.

Cảnh báo? Có thật không? Quá nhiều nhà phát triển trong phòng!

Tôi cũng ngạc nhiên bởi số lượng các hướng dẫn trực tuyến không đề cập đến cách hoạt động lưu có thể không thành công vì những lý do này. Vì vậy, bạn sẽ cần phải đảm bảo rằng bất kỳ sự kiện lưu nào trong Ứng dụng của bạn đều có thể không thành công vì thiết bị CHỈ CÒN PHÚT NÀY đã đầy với việc lưu ứng dụng lưu ứng dụng của bạn.


Câu hỏi này là về việc lưu trong ngăn xếp Dữ liệu cốt lõi, không phải về việc thiết lập Ngăn xếp dữ liệu lõi. Nhưng tôi đồng ý rằng tiêu đề của nó có thể gây hiểu lầm và có lẽ nó nên được sửa đổi.
valeCocoa

Tôi không đồng ý @valeCocoa. Bài đăng nói rõ về cách xử lý lỗi lưu trong sản xuất. Hãy nhìn lại.

@roddanash, đó là những gì tôi đã nói… WtH! :) Hãy nhìn lại câu trả lời của bạn.
valeCocoa 14/09/17

Anh thật điên rồ

bạn dán một phần tài liệu cho các lỗi có thể xảy ra khi khởi tạo kho lưu trữ liên tục về một câu hỏi liên quan đến các lỗi xảy ra trong khi lưu ngữ cảnh, và tôi là người điên? Được…
valeCocoa

5

Tôi thấy chức năng lưu chung này là một giải pháp tốt hơn nhiều:

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

Bất cứ khi nào lưu không thành công, điều này sẽ khôi phục NSManagedObjectContext của bạn có nghĩa là nó sẽ đặt lại tất cả các thay đổi đã được thực hiện trong ngữ cảnh kể từ lần lưu cuối cùng . Vì vậy, bạn phải chú ý cẩn thận để luôn thay đổi liên tục bằng cách sử dụng chức năng lưu trên càng sớm và thường xuyên càng tốt vì nếu không bạn có thể rất dễ mất dữ liệu.

Đối với việc chèn dữ liệu, đây có thể là một biến thể lỏng hơn cho phép các thay đổi khác tồn tại:

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

Lưu ý: Tôi đang sử dụng CocoaLumberjack để đăng nhập ở đây.

Bất kỳ nhận xét nào về cách cải thiện điều này sẽ được chào đón nhiều hơn!

BR Chris


Tôi nhận được hành vi kỳ lạ khi cố gắng sử dụng tính năng khôi phục để đạt được điều này: stackoverflow.com/questions/34426719/…
malhal

Thay vào đó, tôi đang sử dụng tính năng hoàn tác
malhal 12/09/2016

2

Tôi đã tạo một phiên bản Swift của câu trả lời hữu ích của @JohannesFahrenkrug có thể hữu ích:

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"
}`
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.