โยนข้อยกเว้นใน Object-c / cocoa


คำตอบ:


528

ฉันใช้[NSException raise:format:]ดังต่อไปนี้:

[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];

9
ฉันชอบวิธีนี้มากกว่าวิธีที่กำหนด@throw([NSException exceptionWith…])เพราะมันกระชับกว่า
Sam Soffes

9
อย่าลืมอ่านข้อแม้ที่สำคัญจากอันตราย ( stackoverflow.com/questions/324284/324805#324805 )
e.James

26
โดยทั่วไปฉันก็ชอบสิ่งนี้เช่นกัน แต่มีหนึ่ง gotcha อาจเป็น Xcode เวอร์ชันปัจจุบันของฉัน แต่ไวยากรณ์ [NSException Ra ... ] ดูเหมือนจะไม่ได้รับการยอมรับโดย parser ว่าเป็นเส้นทางออกจากวิธีการที่ส่งกลับค่า ฉันเห็นคำเตือน "การควบคุมอาจถึงจุดสิ้นสุดของฟังก์ชั่นที่ไม่เป็นโมฆะ" เมื่อใช้ไวยากรณ์นี้ แต่ด้วย @throw ([NSException exceptionWith ... ]) ไวยากรณ์ parser ตระหนักว่าเป็นการออกและไม่แสดงคำเตือน
mattorb

1
@mpstx ฉันมักจะใช้ไวยากรณ์การโยนสำหรับเหตุผลที่คุณได้รับ (ซึ่งยังคงเกี่ยวข้องในอีกสองปีต่อมาใน Xcode 4.6 และอาจจะเป็นเสมอ) การให้ IDE รับรู้ว่าการทิ้งข้อยกเว้นเป็นจุดออกของฟังก์ชันบ่อยครั้งหากคุณต้องการหลีกเลี่ยงคำเตือน
Mark Amery

FWIW ฉันสังเกตเห็นว่า @ try / @ catch blocks ยังส่งผลให้เกิดคำเตือนที่เป็นลบสำหรับ "การควบคุมถึงจุดสิ้นสุดของฟังก์ชั่นที่ไม่เป็นโมฆะ" (เช่นคำเตือนจะไม่ปรากฏเมื่อมันควรจะเป็น)
Brian Gerstle

256

คำเตือนที่นี่ ใน Objective-C ซึ่งแตกต่างจากภาษาอื่นที่คล้ายคลึงกันโดยทั่วไปคุณควรพยายามหลีกเลี่ยงการใช้ข้อยกเว้นสำหรับสถานการณ์ข้อผิดพลาดทั่วไปที่อาจเกิดขึ้นในการทำงานปกติ

เอกสารประกอบของ Apple สำหรับ Obj-C 2.0ระบุสิ่งต่อไปนี้: "สำคัญ: ข้อยกเว้นมีทรัพยากรจำนวนมากใน Objective-C คุณไม่ควรใช้ข้อยกเว้นสำหรับการควบคุมการไหลทั่วไปหรือเพียงเพื่อบ่งบอกถึงข้อผิดพลาด (เช่นไฟล์

เอกสารการจัดการข้อยกเว้นทางแนวคิดของ Appleอธิบายเหมือนกัน แต่มีคำเพิ่มเติม: "สำคัญ: คุณควรสำรองการใช้ข้อยกเว้นสำหรับการเขียนโปรแกรมหรือข้อผิดพลาดรันไทม์ที่ไม่คาดคิดเช่นการเข้าถึงคอลเลกชันที่ไม่อยู่ในขอบเขต และสูญเสียการเชื่อมต่อกับเซิร์ฟเวอร์หน้าต่างคุณมักจะดูแลข้อผิดพลาดประเภทต่าง ๆ โดยมีข้อยกเว้นเมื่อมีการสร้างแอปพลิเคชันมากกว่าที่รันไทม์ [..... ] แทนที่จะเป็นข้อยกเว้นวัตถุข้อผิดพลาด (NSError) และ กลไกการส่งมอบข้อผิดพลาดโกโก้เป็นวิธีที่แนะนำในการสื่อสารข้อผิดพลาดที่คาดหวังในแอปพลิเคชัน Cocoa "

เหตุผลนี้ส่วนหนึ่งเป็นไปตามการเขียนโปรแกรมสำนวนใน Objective-C (ใช้ค่าตอบแทนในกรณีที่ง่ายและพารามิเตอร์อ้างอิงโดย (มักจะคลาส NSError) ในกรณีที่ซับซ้อนมากขึ้น) ส่วนที่โยนและจับข้อยกเว้นมีราคาแพงมากและ ในที่สุด (และ perpaps ที่สำคัญที่สุด) ว่าข้อยกเว้น Objective-C เป็น wrapper บาง ๆ รอบฟังก์ชั่น setjmp () และ longjmp () ของ C โดยพื้นฐานแล้วทำให้การจัดการหน่วยความจำที่ระมัดระวังดูที่คำอธิบายนี้


11
ฉันคิดว่าสิ่งนี้ใช้ได้กับภาษาการเขียนโปรแกรมส่วนใหญ่: "พยายามหลีกเลี่ยงการใช้ข้อยกเว้นสำหรับสถานการณ์ข้อผิดพลาดทั่วไป" เช่นเดียวกับใน Java; เป็นการปฏิบัติที่ไม่ถูกต้องในการจัดการข้อผิดพลาดในการป้อนข้อมูลผู้ใช้ (ตัวอย่าง) โดยมีข้อยกเว้น ไม่ใช่เพียงเพราะการใช้ทรัพยากร แต่ยังรวมถึงความชัดเจนของรหัสด้วย
beetstra

6
ที่สำคัญกว่านั้นข้อยกเว้นใน Cocoa ออกแบบมาเพื่อระบุข้อผิดพลาดของโปรแกรมที่ไม่สามารถกู้คืนได้ การทำอย่างอื่นกำลังทำงานกับเฟรมเวิร์กและสามารถนำไปสู่พฤติกรรมที่ไม่ได้กำหนด ดูstackoverflow.com/questions/3378696/iphone-try-end-try/…สำหรับรายละเอียด
KPM

9
"เช่นเดียวกับใน Java;" ไม่เห็นด้วย คุณสามารถใช้ข้อยกเว้นที่ตรวจสอบใน Java ได้ดีสำหรับเงื่อนไขข้อผิดพลาดปกติ แน่นอนคุณจะไม่ใช้ข้อยกเว้น Runtime
Daniel Ryan

ฉันชอบวิธี Cocoa (ข้อยกเว้นสำหรับข้อผิดพลาดโปรแกรมเมอร์เท่านั้น) ดังนั้นฉันชอบที่จะทำใน Java เช่นกัน แต่ความจริงก็คือคุณควรไปกับการปฏิบัติทั่วไปในสภาพแวดล้อมและข้อยกเว้นสำหรับการจัดการข้อผิดพลาดออกมาเหมือน มีกลิ่นเหม็นใน Objective-C แต่มีการใช้งานจำนวนมากเพื่อจุดประสงค์ใน Java
gnasher729

1
ความคิดเห็นนี้ไม่ตอบคำถาม บางที OP แค่ต้องการที่จะผิดพลาดของแอพเพื่อทดสอบว่ากรอบรายงานข้อผิดพลาดทำงานได้ตามที่คาดไว้หรือไม่
Simon

62
@throw([NSException exceptionWith…])

Xcode รับรู้@throwคำสั่งเป็นฟังก์ชั่นจุดออกเช่นreturnคำสั่ง การใช้@throwไวยากรณ์หลีกเลี่ยงความผิดพลาด " การควบคุมอาจจะถึงจุดสิ้นสุดของฟังก์ชั่นที่ไม่เป็นโมฆะ " [NSException raise:…]คำเตือนว่าคุณอาจได้รับจาก

นอกจากนี้ยัง@throwสามารถใช้ในการโยนวัตถุที่ไม่ได้อยู่ในคลาส NSException


11
@Steph Thirion: ดูdeveloper.apple.com/documentation/Cocoa/Conceptual/Exceptions/…สำหรับรายละเอียดทั้งหมด บรรทัดล่าง ทั้งสองจะทำงานได้ แต่ @throw สามารถใช้ในการโยนวัตถุที่ไม่ได้อยู่ในคลาส NSException
e.James

33

[NSException raise:format:]เกี่ยวกับ สำหรับผู้ที่มาจากพื้นหลัง Java คุณจะจำได้ว่า Java แตกต่างระหว่างข้อยกเว้นและ RuntimeException ข้อยกเว้นเป็นข้อยกเว้นที่ตรวจสอบและ RuntimeException ไม่ถูกตรวจสอบ โดยเฉพาะอย่างยิ่ง Java แนะนำให้ใช้ข้อยกเว้นที่ตรวจสอบแล้วสำหรับ "เงื่อนไขข้อผิดพลาดปกติ" และข้อยกเว้นที่ไม่ได้ตรวจสอบสำหรับ "ข้อผิดพลาดรันไทม์ที่เกิดจากข้อผิดพลาดของโปรแกรมเมอร์" ดูเหมือนว่าควรใช้ข้อยกเว้น Objective-C ในสถานที่เดียวกันกับที่คุณจะใช้ข้อยกเว้นที่ไม่ได้ตรวจสอบและควรใช้ค่าส่งคืนรหัสข้อผิดพลาดหรือค่า NSError ในสถานที่ที่คุณจะใช้ข้อยกเว้นที่ตรวจสอบ


1
ใช่สิ่งนี้ถูกต้อง (หลังจาก 4 ปีแม้ว่า: D) สร้างคลาสข้อผิดพลาดของคุณเอง ABCError ซึ่งขยายจากคลาส NSError และใช้มันสำหรับการตรวจสอบข้อยกเว้นมากกว่า NSExceptions เพิ่ม NSExceptions ที่มีข้อผิดพลาดของโปรแกรมเมอร์ (สถานการณ์ที่ไม่คาดคิดเช่นปัญหาเกี่ยวกับรูปแบบตัวเลข)
chathuram

15

ฉันคิดว่าจะสอดคล้องกันดีกว่าที่จะใช้ @throw กับคลาสของคุณเองที่ขยาย NSException จากนั้นคุณใช้สัญลักษณ์เดียวกันเพื่อลองจับในที่สุด:

@try {
.....
}
@catch{
...
}
@finally{
...
}

Apple อธิบายวิธีโยนและจัดการข้อยกเว้นที่นี่: การ จับข้อยกเว้นการ โยนข้อยกเว้น


ฉันยังคงมีข้อผิดพลาดจากข้อยกเว้นรันไทม์ในบล็อกลอง
famfamfam

14

เนื่องจาก ObjC 2.0 ข้อยกเว้น Objective-C ไม่ใช่กระดาษห่อสำหรับ setjmp () longjmp () ของ C อีกต่อไปและเข้ากันได้กับข้อยกเว้น C ++, @try นั้น "ไม่มีค่าใช้จ่าย" แต่การโยนและจับข้อยกเว้นมีราคาแพงกว่า

อย่างไรก็ตามการยืนยัน (การใช้ NSAssert และ NSCAssert macro family) จะเป็นการโยน NSException และมีเหตุผลที่จะใช้มันเป็นสถานะ Ries


ดีแล้วที่รู้! เรามีห้องสมุดบุคคลที่สามซึ่งเราไม่ต้องการที่จะแก้ไขข้อผิดพลาดแม้แต่น้อยที่สุด เราต้องจับพวกมันไว้ในที่เดียวในแอพและมันทำให้เราประจบประแจง แต่สิ่งนี้ทำให้ฉันรู้สึกดีขึ้นเล็กน้อย
Yuri Brigance

8

ใช้ NSError เพื่อสื่อสารความล้มเหลวมากกว่าข้อยกเว้น

จุดด่วนเกี่ยวกับ NSError:

  • NSError อนุญาตให้รหัสข้อผิดพลาดสไตล์ C (จำนวนเต็ม) ระบุสาเหตุที่ชัดเจนและหวังว่าจะช่วยให้ตัวจัดการข้อผิดพลาดสามารถเอาชนะข้อผิดพลาดได้ คุณสามารถตัดรหัสข้อผิดพลาดจากไลบรารี C เช่น SQLite ในอินสแตนซ์ NSError ได้อย่างง่ายดาย

  • NSError ยังมีประโยชน์ในการเป็นวัตถุและเสนอวิธีการอธิบายข้อผิดพลาดในรายละเอียดเพิ่มเติมกับสมาชิกพจนานุกรม userInfo

  • แต่ที่ดีที่สุดคือ NSError ไม่สามารถถูกโยนได้ดังนั้นจึงส่งเสริมวิธีการจัดการข้อผิดพลาดเชิงรุกมากกว่าภาษาอื่น ๆ ที่เพียงแค่โยนมันฝรั่งร้อนไปไกลกว่านั้น ไม่ได้รับการจัดการด้วยวิธีที่มีความหมายใด ๆ (ไม่ใช่หากคุณเชื่อในการติดตามหลักการที่ใหญ่ที่สุดของ OOP ในการซ่อนข้อมูลนั่นคือ)

ลิงค์อ้างอิง : การอ้างอิง


ความคิดเห็นนี้ไม่ตอบคำถาม บางที OP แค่ต้องการที่จะผิดพลาดของแอพเพื่อทดสอบว่ากรอบรายงานข้อผิดพลาดทำงานได้ตามที่คาดไว้หรือไม่
Simon

7

นี่คือวิธีที่ฉันเรียนรู้จาก "The Big Nerd Ranch Guide (รุ่นที่ 4)":

@throw [NSException exceptionWithName:@"Something is not right exception"
                               reason:@"Can't perform this operation because of this or that"
                             userInfo:nil];

ตกลง userInfo:nilแต่ก็ไม่ได้บอกมากเกี่ยวกับ :)
Cœur

6

คุณสามารถใช้สองวิธีในการเพิ่มข้อยกเว้นในลอง catch catch block

@throw[NSException exceptionWithName];

หรือวิธีที่สอง

NSException e;
[e raise];

3

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

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

Ries


0

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


0

โค้ดตัวอย่างสำหรับกรณี: @throw ([NSException exceptionWithName: ...

- (void)parseError:(NSError *)error
       completionBlock:(void (^)(NSString *error))completionBlock {


    NSString *resultString = [NSString new];

    @try {

    NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];

    if(!errorData.bytes) {

        @throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
    }


    NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
                                                                 options:NSJSONReadingAllowFragments
                                                                   error:&error];

    resultString = dictFromData[@"someKey"];
    ...


} @catch (NSException *exception) {

      NSLog( @"Caught Exception Name: %@", exception.name);
      NSLog( @"Caught Exception Reason: %@", exception.reason );

    resultString = exception.reason;

} @finally {

    completionBlock(resultString);
}

}

โดยใช้:

[self parseError:error completionBlock:^(NSString *error) {
            NSLog(@"%@", error);
        }];

อีกกรณีการใช้งานขั้นสูง:

- (void)parseError:(NSError *)error completionBlock:(void (^)(NSString *error))completionBlock {

NSString *resultString = [NSString new];

NSException* customNilException = [NSException exceptionWithName:@"NilException"
                                                          reason:@"object is nil"
                                                        userInfo:nil];

NSException* customNotNumberException = [NSException exceptionWithName:@"NotNumberException"
                                                                reason:@"object is not a NSNumber"
                                                              userInfo:nil];

@try {

    NSData *errorData = [NSData dataWithData:error.userInfo[@"SomeKeyForData"]];

    if(!errorData.bytes) {

        @throw([NSException exceptionWithName:@"<Set Yours exc. name: > Test Exc" reason:@"<Describe reason: > Doesn't contain data" userInfo:nil]);
    }


    NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:errorData
                                                                 options:NSJSONReadingAllowFragments
                                                                   error:&error];

    NSArray * array = dictFromData[@"someArrayKey"];

    for (NSInteger i=0; i < array.count; i++) {

        id resultString = array[i];

        if (![resultString isKindOfClass:NSNumber.class]) {

            [customNotNumberException raise]; // <====== HERE is just the same as: @throw customNotNumberException;

            break;

        } else if (!resultString){

            @throw customNilException;        // <======

            break;
        }

    }

} @catch (SomeCustomException * sce) {
    // most specific type
    // handle exception ce
    //...
} @catch (CustomException * ce) {
    // most specific type
    // handle exception ce
    //...
} @catch (NSException *exception) {
    // less specific type

    // do whatever recovery is necessary at his level
    //...
    // rethrow the exception so it's handled at a higher level

    @throw (SomeCustomException * customException);

} @finally {
    // perform tasks necessary whether exception occurred or not

}

}


-7

ไม่มีเหตุผลที่จะไม่ใช้ข้อยกเว้นตามปกติในวัตถุประสงค์ C แม้ว่าจะหมายถึงข้อยกเว้นกฎธุรกิจ Apple สามารถพูดได้ว่าใช้ NSError ผู้ใส่ใจ Obj C ใช้เวลานานมากและในครั้งเดียวเอกสาร C ++ ทั้งหมดกล่าวในสิ่งเดียวกัน เหตุผลที่มันไม่สำคัญว่าการโยนและการยกเว้นมีราคาแพงเพียงใดคืออายุการใช้งานของข้อยกเว้นนั้นสั้นมากเหลือเกินและ ... เป็นข้อยกเว้นของการไหลปกติ ฉันไม่เคยได้ยินใครพูดในชีวิตของฉันคนที่ยกเว้นใช้เวลานานในการโยนและจับ

นอกจากนี้ยังมีคนที่คิดว่าวัตถุประสงค์ C เองนั้นแพงเกินไปและใช้รหัสใน C หรือ C ++ แทน ดังนั้นการบอกว่าใช้ NSError เสมอไม่ทราบและหวาดระแวง

แต่คำถามของเธรดนี้ยังไม่ได้รับการตอบคืออะไรคือวิธีที่ดีที่สุดในการยกเว้น วิธีคืน NSError นั้นชัดเจน

ดังนั้นมัน: [เพิ่ม NSException: ... @throw [[จัดสรร NSException] initWithName .... หรือ @throw [[MyCustomException ... ?

ฉันใช้กฎที่เลือก / ไม่ได้ตรวจสอบที่นี่แตกต่างจากด้านบนเล็กน้อย

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

ดังนั้นฉันจึงใช้คลาสยกเว้นที่กำหนดเองด้วย @throw สำหรับข้อยกเว้นที่กู้คืนได้เนื่องจากเป็นไปได้ว่าฉันจะมีวิธีการแอปบางอย่างเพื่อค้นหาความล้มเหลวบางประเภทในบล็อก @catch หลาย ๆ ชุด ตัวอย่างเช่นหากแอพของฉันเป็นเครื่อง ATM ฉันจะมี @catch block สำหรับ "WithdrawalRequestExceedsBalanceException"

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

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


4
ฉันขอแนะนำให้พยายามเขียนโปรแกรมเซิร์ฟเวอร์ที่มีข้อยกเว้นซึ่งเป็นส่วนหนึ่งของโฟลว์กรณีข้อผิดพลาดตามปกติก่อนที่จะทำการแถลงทั่วไปเช่น "ไม่มีเหตุผลที่จะไม่ใช้ข้อยกเว้นตามปกติในวัตถุประสงค์ C" เชื่อหรือไม่ว่ามีเหตุผลในการเขียนแอปพลิเคชั่นที่มีประสิทธิภาพสูง (หรืออย่างน้อยส่วนหนึ่งของแอปพลิเคชัน) ใน ObjC และการโยนข้อยกเว้นตามปกติจะทำให้ประสิทธิภาพการทำงานแย่ลง
jbenet

6
มีเหตุผลที่ดีมากอย่างแน่นอนว่าทำไมไม่ใช้ข้อยกเว้นในโกโก้ ดูคำตอบของบิล Bumgarner นี่สำหรับข้อมูลเพิ่มเติม: stackoverflow.com/questions/3378696/iphone-try-end-try/... เขารู้ว่าเขากำลังพูดถึงเรื่องอะไร (คำใบ้: ตรวจสอบนายจ้างของเขา) ข้อยกเว้นใน Cocoa ถือว่าเป็นข้อผิดพลาดที่ไม่สามารถกู้คืนได้และสามารถทำให้ระบบอยู่ในสถานะไม่เสถียร NSError เป็นวิธีการส่งผ่านข้อผิดพลาดทั่วไปรอบ ๆ
แบรดลาร์สัน

จะมีการยกเว้นเป็นพิเศษ ความล้มเหลวของกฎธุรกิจย่อมไม่ผ่านการรับรอง "การค้นหาและออกแบบรหัสที่มีข้อยกเว้นอย่างหนักอาจส่งผลให้ชนะได้อย่างสมบูรณ์แบบ" MSDN ผ่านcodinghorror.com/blog/2004/10/…
Jonathan Watmough

3
ข้อยกเว้นไม่สามารถโยนจากบล็อก ข้อยกเว้นที่เกิดขึ้นในสภาพแวดล้อม ARC อาจทำให้โปรแกรมของคุณรั่วไหล ดังนั้นการลงคะแนน
Moszi

"มันไม่สำคัญว่าการโยนและการยกเว้นที่มีราคาแพงมากแค่ไหน" ฉันกำลังเขียนโปรแกรมจำลองซึ่งประสิทธิภาพเป็นสิ่งสำคัญ ฉันไม่สามารถโยนข้อยกเว้นราคาแพงทั้งกลุ่มได้
NobodyNada
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.