การจัดการข้อผิดพลาด "การผลิต" ข้อมูลหลักของ iPhone


85

ฉันเห็นในโค้ดตัวอย่างที่ Apple ให้มาอ้างถึงวิธีจัดการกับข้อผิดพลาดของ Core Data ได้แก่ :

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


8
+1 นี่เป็นคำถามที่ยอดเยี่ยม
Dave DeLong

คำตอบ:


33

จะไม่มีใครแสดงรหัสการผลิตให้คุณเห็นเพราะขึ้นอยู่กับแอปพลิเคชันของคุณ 100% และจุดที่เกิดข้อผิดพลาด

โดยส่วนตัวแล้วฉันใส่ข้อความยืนยันไว้ที่นั่นเพราะ 99.9% ของเวลาที่ข้อผิดพลาดนี้จะเกิดขึ้นในการพัฒนาและเมื่อคุณแก้ไขมันไม่น่าเป็นไปได้สูงที่คุณจะเห็นมันในการผลิต

หลังจากยืนยันแล้วฉันจะแจ้งเตือนผู้ใช้แจ้งให้พวกเขาทราบว่ามีข้อผิดพลาดที่ไม่สามารถกู้คืนได้เกิดขึ้นและแอปพลิเคชันกำลังจะออก นอกจากนี้คุณยังสามารถแจ้งให้ทราบล่วงหน้าเพื่อขอให้พวกเขาติดต่อนักพัฒนาเพื่อที่คุณจะได้ติดตามสิ่งนี้ได้

หลังจากนั้นฉันจะทิ้ง abort () ไว้ที่นั่นเพราะมันจะ "ขัดข้อง" แอพและสร้างสแต็กแทร็กที่คุณหวังว่าจะใช้ในภายหลังเพื่อติดตามปัญหา


Marcus - แม้ว่าการยืนยันจะใช้ได้ดีหากคุณกำลังพูดคุยกับฐานข้อมูล sqlite ในเครื่องหรือไฟล์ XML คุณต้องมีกลไกการจัดการข้อผิดพลาดที่มีประสิทธิภาพมากขึ้นหากที่เก็บถาวรของคุณใช้ระบบคลาวด์
dar512

4
หากที่เก็บถาวรของ iOS Core Data เป็นระบบคลาวด์คุณจะมีปัญหาใหญ่กว่า
Marcus S.Zarra

3
ฉันไม่เห็นด้วยกับ Apple ในหลายหัวข้อ มันเป็นความแตกต่างระหว่างสถานการณ์การสอน (Apple) และในสนามเพลาะ (ฉัน) จากสถานการณ์ทางวิชาการคุณควรยกเลิกการทำแท้ง ในความเป็นจริงมันมีประโยชน์ในการจับสถานการณ์ที่คุณไม่เคยคิดว่าจะเป็นไปได้ ผู้เขียนเอกสารของ Apple ชอบแสร้งทำเป็นว่าทุกสถานการณ์ต้องรับผิดชอบ 99.999% ของพวกเขาคือ. คุณทำอะไรให้กับสิ่งที่ไม่คาดคิดอย่างแท้จริง? ฉันขัดข้องและสร้างบันทึกเพื่อที่ฉันจะได้ทราบว่าเกิดอะไรขึ้น นั่นคือสิ่งที่ทำแท้งมีไว้เพื่อ
Marcus S.Zarra

1
@cschuff ไม่มีผลกระทบต่อการ-save:โทรข้อมูลหลัก เงื่อนไขทั้งหมดนั้นเกิดขึ้นนานก่อนที่รหัสของคุณจะมาถึงจุดนี้
Marcus S.Zarra

3
นั่นคือข้อผิดพลาดที่คาดการณ์ไว้ซึ่งสามารถตรวจจับและแก้ไขได้ก่อนบันทึก คุณสามารถถาม Core Data ว่าข้อมูลถูกต้องและถูกต้องหรือไม่ นอกจากนี้คุณสามารถทดสอบว่าในขณะบริโภคเพื่อให้แน่ใจว่ามีฟิลด์ที่ถูกต้องทั้งหมด นั่นคือข้อผิดพลาดระดับนักพัฒนาที่สามารถจัดการได้นานก่อนที่-save:จะเรียก
Marcus S.Zarra

32

นี่เป็นวิธีการทั่วไปวิธีหนึ่งที่ฉันคิดขึ้นเพื่อจัดการและแสดงข้อผิดพลาดในการตรวจสอบความถูกต้องบน iPhone แต่มาร์คัสพูดถูก: คุณอาจต้องการปรับแต่งข้อความให้เป็นมิตรกับผู้ใช้มากขึ้น แต่อย่างน้อยสิ่งนี้ก็ช่วยให้คุณมีจุดเริ่มต้นในการดูว่าฟิลด์ใดไม่ผ่านการตรวจสอบและทำไม

- (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 ด้วยการยืนยัน ฉันยังไม่เห็นว่ามีการผลิตดังนั้นฉันจึงถือว่าพวกเขาเป็นข้อผิดพลาดในการพัฒนามากกว่าข้อผิดพลาดในการผลิตที่อาจเกิดขึ้น แม้ว่านี่จะเป็นการป้องกันอีกระดับอย่างแน่นอน :)
Marcus S.Zarra

2
Marcus เกี่ยวกับการยืนยัน: คุณมีความคิดเห็นอย่างไรเกี่ยวกับการรักษารหัส DRY ในแง่ของการตรวจสอบความถูกต้อง ในความคิดของฉันเป็นที่พึงปรารถนาอย่างยิ่งที่จะกำหนดเกณฑ์การตรวจสอบของคุณเพียงครั้งเดียวในโมเดล (ที่เป็นของ): ฟิลด์นี้ต้องไม่ว่างเปล่าฟิลด์นั้นจะต้องมีความยาวอย่างน้อย 5 ตัวอักษรและฟิลด์นั้นจะต้องตรงกับนิพจน์ทั่วไปนี้ . นั่นควรเป็นข้อมูลทั้งหมดที่จำเป็นในการแสดงข้อความที่เหมาะสมแก่ผู้ใช้ มันไม่เหมาะกับฉันที่จะทำการตรวจสอบเหล่านั้นอีกครั้งในรหัสก่อนบันทึก MOC คุณคิดอย่างไร?
Johannes Fahrenkrug

2
ไม่เคยเห็นความคิดเห็นนี้เนื่องจากมันไม่ได้อยู่ในคำตอบของฉัน แม้ว่าคุณจะใส่การตรวจสอบความถูกต้องในโมเดลคุณก็ยังต้องตรวจสอบเพื่อดูว่าออบเจ็กต์นั้นผ่านการตรวจสอบความถูกต้องหรือไม่และนำเสนอให้กับผู้ใช้ ขึ้นอยู่กับการออกแบบที่อาจอยู่ในระดับฟิลด์ (รหัสผ่านนี้ไม่ถูกต้อง ฯลฯ ) หรือที่จุดบันทึก ทางเลือกของนักออกแบบ ฉันจะไม่สร้างส่วนนั้นของแอปทั่วไป
Marcus S.Zarra

1
@ MarcusS.Zarra ฉันเดาว่าคุณไม่เคยได้รับมันเพราะฉันทำไม่ถูกต้อง @ - กล่าวถึงคุณ :) ฉันคิดว่าเราเห็นด้วยอย่างเต็มที่: ฉันต้องการให้ข้อมูลการตรวจสอบความถูกต้องอยู่ในแบบจำลอง แต่การตัดสินใจว่าจะเริ่มการตรวจสอบความถูกต้องและ วิธีจัดการและนำเสนอผลการตรวจสอบความถูกต้องไม่ควรเป็นแบบทั่วไปและควรจัดการในตำแหน่งที่เหมาะสมในรหัสแอปพลิเคชัน
Johannes Fahrenkrug

โค้ดดูดีมาก คำถามเดียวของฉันคือหลังจากแสดงการแจ้งเตือนหรือบันทึกการวิเคราะห์แล้วฉันควรย้อนกลับบริบทข้อมูลหลักหรือยกเลิกแอป? มิฉะนั้นฉันเดาว่าการเปลี่ยนแปลงที่ไม่ได้บันทึกจะทำให้เกิดปัญหาเดิมเมื่อคุณพยายามบันทึกอีกครั้ง
Jake

6

ฉันแปลกใจที่ไม่มีใครจัดการข้อผิดพลาดอย่างที่ควรจะจัดการ หากคุณดูเอกสารคุณจะเห็น

สาเหตุทั่วไปของข้อผิดพลาดในที่นี้ ได้แก่ : * อุปกรณ์ไม่มีพื้นที่เหลือ * ไม่สามารถเข้าถึงที่เก็บถาวรได้เนื่องจากการอนุญาตหรือการปกป้องข้อมูลเมื่ออุปกรณ์ถูกล็อค * ไม่สามารถย้ายร้านค้าไปยังรุ่นรุ่นปัจจุบันได้ * ไม่มีไดเร็กทอรีหลักไม่สามารถสร้างหรือปิดการเขียนได้

ดังนั้นหากฉันพบข้อผิดพลาดเมื่อตั้งค่าสแต็กข้อมูลหลักฉันจะสลับ rootViewController ของ UIWindow และแสดง UI ที่บอกผู้ใช้อย่างชัดเจนว่าอุปกรณ์ของพวกเขาอาจเต็มหรือการตั้งค่าความปลอดภัยสูงเกินไปที่แอปนี้จะทำงานได้ ฉันยังให้ปุ่ม 'ลองอีกครั้ง' แก่พวกเขาเพื่อให้พวกเขาสามารถพยายามแก้ไขปัญหาก่อนที่จะลองสแตกข้อมูลหลักอีกครั้ง

ตัวอย่างเช่นผู้ใช้สามารถเพิ่มพื้นที่เก็บข้อมูลให้กลับไปที่แอพของฉันแล้วกดปุ่มลองอีกครั้ง

ยืนยัน? จริงๆ? มีนักพัฒนาในห้องนี้มากเกินไป!

ฉันยังประหลาดใจกับจำนวนบทเรียนออนไลน์ที่ไม่ได้กล่าวถึงว่าการดำเนินการบันทึกอาจล้มเหลวด้วยเหตุผลเหล่านี้ได้อย่างไร ดังนั้นคุณจะต้องตรวจสอบให้แน่ใจว่าเหตุการณ์การบันทึกไม่ว่าที่ใดก็ตามในแอปของคุณอาจล้มเหลวเนื่องจากอุปกรณ์ในนาทีนี้เต็มด้วยแอปของคุณที่บันทึกการบันทึกการบันทึก


คำถามนี้เกี่ยวกับการประหยัดใน Core Data stack ไม่ใช่เรื่องการตั้งค่า Core Data Stack แต่ฉันยอมรับว่าชื่อของมันอาจทำให้เข้าใจผิดและควรแก้ไข
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.