ฉันจะใช้ NSError ในแอพ iPhone ของฉันได้อย่างไร


228

ฉันกำลังหาข้อผิดพลาดในแอพของฉันและฉันกำลังใช้งานNSErrorอยู่ ฉันสับสนเล็กน้อยเกี่ยวกับวิธีการใช้งานและวิธีเติมข้อมูล

มีคนให้ตัวอย่างเกี่ยวกับวิธีที่ฉันเติมแล้วใช้NSError?

คำตอบ:


473

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

ตัวอย่าง:

- (id) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return nil;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

จากนั้นเราสามารถใช้วิธีการเช่นนี้ อย่าแม้แต่จะตรวจสอบวัตถุข้อผิดพลาดยกเว้นว่าวิธีคืนค่าเป็นศูนย์:

// initialize NSError object
NSError* error = nil;
// try to feed the world
id yayOrNay = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!yayOrNay) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.

เราสามารถเข้าถึงข้อผิดพลาดได้localizedDescriptionเพราะเราตั้งค่าNSLocalizedDescriptionKeyไว้

สถานที่ที่ดีที่สุดสำหรับข้อมูลเพิ่มเติมเอกสารแอปเปิ้ล มันดีจริงๆ

นอกจากนี้ยังมีความสุข, กวดวิชาที่ง่ายในโกโก้คือแฟนของฉัน


37
นี่เป็นตัวอย่างที่สนุกที่สุดตลอดกาล
ming yeow

นี้เป็นคำตอบที่น่ากลัวสวยแม้ว่าจะมีบางประเด็นใน ARC และหล่อไปid BOOLรูปแบบที่เข้ากันได้กับ ARC เล็กน้อยจะได้รับการชื่นชมมาก
NSTJ

6
@ TomJowett ฉันจะโกรธจริง ๆ ถ้าเราไม่สามารถยุติความหิวโหยในโลกได้เพราะ Apple ผลักดันให้เราย้ายไปสู่โลก ARC ที่ใหม่กว่าเท่านั้น
Manav

1
BOOLประเภทผลตอบแทนที่สามารถ ย้อนกลับไปในกรณีของความผิดพลาดและแทนการตรวจสอบสำหรับค่าตอบแทนเพียงแค่ตรวจสอบNO errorถ้าnilไปข้างหน้าถ้า!= nilจัดการกับมัน
Gabriele Petronella

8
-1: คุณจำเป็นต้องรวมรหัสที่ตรวจสอบแล้ว**errorไม่ใช่ศูนย์ มิฉะนั้นโปรแกรมจะส่งข้อผิดพลาดซึ่งไม่เป็นมิตรอย่างสมบูรณ์และไม่ทำให้เกิดสิ่งที่ชัดเจน
FreeAsInBeer

58

ฉันต้องการเพิ่มคำแนะนำเพิ่มเติมตามการใช้งานครั้งล่าสุดของฉัน ฉันดูโค้ดจาก Apple และฉันคิดว่าโค้ดของฉันทำงานในลักษณะเดียวกัน

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


ฉันขอแนะนำให้สร้างส่วนหัว 1 รายการซึ่งจะเป็นภาพรวมของข้อผิดพลาดทั้งหมดในโดเมนของคุณ (เช่นแอปห้องสมุด ฯลฯ ) ส่วนหัวปัจจุบันของฉันมีลักษณะเช่นนี้:

FSError.h

FOUNDATION_EXPORT NSString *const FSMyAppErrorDomain;

enum {
    FSUserNotLoggedInError = 1000,
    FSUserLogoutFailedError,
    FSProfileParsingFailedError,
    FSProfileBadLoginError,
    FSFNIDParsingFailedError,
};

FSError.m

#import "FSError.h" 

NSString *const FSMyAppErrorDomain = @"com.felis.myapp";

ตอนนี้เมื่อใช้ค่าข้างต้นเพื่อหาข้อผิดพลาด Apple จะสร้างข้อความแสดงข้อผิดพลาดมาตรฐานขั้นพื้นฐานสำหรับแอปของคุณ ข้อผิดพลาดสามารถสร้างได้ดังต่อไปนี้:

+ (FSProfileInfo *)profileInfoWithData:(NSData *)data error:(NSError **)error
{
    FSProfileInfo *profileInfo = [[FSProfileInfo alloc] init];
    if (profileInfo)
    {
        /* ... lots of parsing code here ... */

        if (profileInfo.username == nil)
        {
            *error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSProfileParsingFailedError userInfo:nil];            
            return nil;
        }
    }
    return profileInfo;
}

ข้อความแสดงข้อผิดพลาดที่สร้างโดย Apple มาตรฐาน ( error.localizedDescription) สำหรับโค้ดด้านบนจะมีลักษณะดังต่อไปนี้:

Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"

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

สำหรับข้อความแสดงข้อผิดพลาดเราต้องคำนึงถึงการแปลเป็นภาษาท้องถิ่น (แม้ว่าเราจะไม่ใช้ข้อความที่แปลเป็นภาษาท้องถิ่นทันที) ฉันใช้วิธีต่อไปนี้ในโครงการปัจจุบันของฉัน:


1) สร้าง stringsไฟล์ที่จะมีข้อผิดพลาด ไฟล์สตริงสามารถแปลได้อย่างง่ายดาย ไฟล์อาจมีลักษณะดังต่อไปนี้:

FSError.strings

"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."

2) เพิ่มมาโครเพื่อแปลงรหัสจำนวนเต็มเป็นข้อความแสดงข้อผิดพลาดที่แปลเป็นภาษาท้องถิ่น ฉันใช้มาโคร 2 ตัวในไฟล์ค่าคงที่ + Macros.h ฉันรวมไฟล์นี้ไว้ในส่วนหัวของคำนำหน้าเสมอ (MyApp-Prefix.pch ) เพื่อความสะดวก

ค่าคงที่ + Macros.h

// error handling ...

#define FS_ERROR_KEY(code)                    [NSString stringWithFormat:@"%d", code]
#define FS_ERROR_LOCALIZED_DESCRIPTION(code)  NSLocalizedStringFromTable(FS_ERROR_KEY(code), @"FSError", nil)

3) ตอนนี้มันง่ายที่จะแสดงข้อความข้อผิดพลาดที่ผู้ใช้เข้าใจง่ายขึ้นอยู่กับรหัสข้อผิดพลาด ตัวอย่าง:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];

9
คำตอบที่ดี! แต่ทำไมไม่ใส่คำอธิบายที่แปลในพจนานุกรมข้อมูลผู้ใช้ที่มันอยู่? [ข้อผิดพลาด NSErrorWithDomain: รหัส FSMyAppErrorDomain: FSProfileParsingFailedError userInfo: @ {NSLocalizedDescriptionKey: FS_ERROR_LOCALIZED_DESCRIPTION (ข้อผิดพลาดรหัส)}];
Richard Venable

1
มีสถานที่เฉพาะที่ฉันควรใส่ไฟล์สตริงหรือไม่ จาก FS_ERROR_LOCALIZED_DESCRIPTION () ฉันได้รับแค่ตัวเลข (รหัสข้อผิดพลาด)
huggie

@huggie: ไม่แน่ใจจริงๆว่าคุณหมายถึงอะไร ฉันมักจะใส่มาโครเหล่านี้ที่ฉันใช้ข้ามแอพทั้งหมดลงในไฟล์ชื่อConstants+Macros.hและนำเข้าไฟล์นี้ในส่วนหัวของคำนำหน้า ( .pchไฟล์) เพื่อให้สามารถใช้ได้ทุกที่ หากคุณหมายความว่าคุณใช้มาโคร 1 ใน 2 ตัวเท่านั้นนั่นอาจใช้ได้ บางทีการแปลงจากintการNSStringไม่จำเป็นจริงๆ แต่ผมไม่ได้ทดสอบนี้
Wolfgang Schreurs

@huggie: โอ้ฉันคิดว่าฉันเข้าใจคุณแล้ว สตริงควรอยู่ในไฟล์ที่แปลได้ ( .stringsไฟล์) เนื่องจากเป็นตำแหน่งที่แมโครของ Apple จะมอง อ่านเกี่ยวกับการใช้งานได้NSLocalizedStringFromTableที่นี่: developer.apple.com/library/mac/documentation/cocoa/conceptual/…
Wolfgang Schreurs

1
@huggie: ใช่ฉันใช้ตารางสตริงที่แปลแล้ว รหัสในแมโครFS_ERROR_LOCALIZED_DESCRIPTIONตรวจสอบสตริง localisable FSError.stringsในไฟล์ที่เรียกว่า คุณอาจต้องการตรวจสอบคำแนะนำการแปลของ Apple เกี่ยวกับ.stringsไฟล์หากสิ่งนี้เป็นสิ่งแปลกใหม่สำหรับคุณ
Wolfgang Schreurs

38

คำตอบที่ดีอเล็กซ์ ปัญหาหนึ่งที่อาจเกิดขึ้นได้คือ Nere dereference การอ้างอิงของ Apple เกี่ยวกับการสร้างและการส่งคืนวัตถุ NSError

...
[details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];

if (error != NULL) {
    // populate the error object with the details
    *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
// we couldn't feed the world's children...return nil..sniffle...sniffle
return nil;
...

30

Objective-C

NSError *err = [NSError errorWithDomain:@"some_domain"
                                   code:100
                               userInfo:@{
                                           NSLocalizedDescriptionKey:@"Something went wrong"
                               }];

สวิฟท์ 3

let error = NSError(domain: "some_domain",
                      code: 100,
                  userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])

9

โปรดอ้างอิงบทช่วยสอนต่อไปนี้

ฉันหวังว่ามันจะเป็นประโยชน์สำหรับคุณ แต่ก่อนที่คุณจะต้องอ่านเอกสารของ NSError

นี่คือลิงค์ที่น่าสนใจมากที่ฉันพบว่าErrorHandlingเมื่อเร็ว ๆ นี้


3

ฉันจะลองสรุปคำตอบที่ยอดเยี่ยมของ Alex และจุดของ jlmendezbonini โดยเพิ่มการดัดแปลงที่จะทำให้ทุกอย่างเข้ากันได้กับ ARC (จนถึงตอนนี้ไม่ใช่เพราะ ARC จะบ่นเพราะคุณควรกลับมาidซึ่งหมายถึง "วัตถุใด ๆ " แต่BOOLไม่ใช่วัตถุ พิมพ์)

- (BOOL) endWorldHunger:(id)largeAmountsOfMonies error:(NSError**)error {
    // begin feeding the world's children...
    // it's all going well until....
    if (ohNoImOutOfMonies) {
        // sad, we can't solve world hunger, but we can let people know what went wrong!
        // init dictionary to be used to populate error object
        NSMutableDictionary* details = [NSMutableDictionary dictionary];
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        // populate the error object with the details
        if (error != NULL) {
             // populate the error object with the details
             *error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
        }
        // we couldn't feed the world's children...return nil..sniffle...sniffle
        return NO;
    }
    // wohoo! We fed the world's children. The world is now in lots of debt. But who cares? 
    return YES;
}

ตอนนี้แทนที่จะตรวจสอบค่าส่งคืนของการเรียกใช้เมธอดของเราเราตรวจสอบว่าerrorยังอยู่nilหรือไม่ ถ้าไม่ใช่เราก็มีปัญหา

// initialize NSError object
NSError* error = nil;
// try to feed the world
BOOL success = [self endWorldHunger:smallAmountsOfMonies error:&error];
if (!success) {
   // inspect error
   NSLog(@"%@", [error localizedDescription]);
}
// otherwise the world has been fed. Wow, your code must rock.

3
@Gabriela: Apple ระบุว่าเมื่อใช้ตัวแปรทางอ้อมเพื่อส่งกลับข้อผิดพลาดวิธีการของตัวเองควรจะมีค่าตอบแทนในกรณีที่ประสบความสำเร็จหรือล้มเหลว แอปเปิ้ลขอเรียกร้องให้นักพัฒนาในการตรวจสอบครั้งแรกสำหรับค่าตอบแทนและเพียงถ้าค่าตอบแทนเป็นอย่างใดตรวจสอบไม่ถูกต้องสำหรับข้อผิดพลาด ดูหน้าต่อไปนี้: developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/
......

3

รูปแบบการออกแบบอื่นที่ฉันได้เห็นเกี่ยวข้องกับการใช้บล็อกซึ่งมีประโยชน์อย่างยิ่งเมื่อมีการเรียกใช้วิธีการแบบไม่พร้อมกัน

สมมติว่าเราได้กำหนดรหัสข้อผิดพลาดต่อไปนี้:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

คุณจะกำหนดวิธีการของคุณที่สามารถทำให้เกิดข้อผิดพลาดดังนี้:

- (void)getContentsOfURL:(NSString *)path success:(void(^)(NSString *html))success failure:(void(^)(NSError *error))failure {
    if (path.length == 0) {
        if (failure) {
            failure([NSError errorWithDomain:@"com.example" code:MyErrorCodesEmptyString userInfo:nil]);
        }
        return;
    }

    NSString *htmlContents = @"";

    // Exercise for the reader: get the contents at that URL or raise another error.

    if (success) {
        success(htmlContents);
    }
}

และเมื่อคุณเรียกใช้คุณไม่จำเป็นต้องกังวลเกี่ยวกับการประกาศวัตถุ NSError (การทำให้โค้ดสมบูรณ์จะทำเพื่อคุณ) หรือตรวจสอบค่าที่ส่งคืน คุณสามารถจัดหาบล็อกสองบล็อก: บล็อกที่จะได้รับเมื่อมีข้อยกเว้นและบล็อกที่จะได้รับเมื่อสำเร็จ:

[self getContentsOfURL:@"http://google.com" success:^(NSString *html) {
    NSLog(@"Contents: %@", html);
} failure:^(NSError *error) {
    NSLog(@"Failed to get contents: %@", error);
    if (error.code == MyErrorCodesEmptyString) { // make sure to check the domain too
        NSLog(@"You must provide a non-empty string");
    }
}];

0

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

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);

0
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

ซึ่งฉันสามารถใช้NSError.defaultError()เมื่อใดก็ตามที่ฉันไม่มีวัตถุข้อผิดพลาดที่ถูกต้อง

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.