เป็นวิธีที่ดีที่สุดในการยกเว้นใน Object-c / cocoa อะไร
เป็นวิธีที่ดีที่สุดในการยกเว้นใน Object-c / cocoa อะไร
คำตอบ:
ฉันใช้[NSException raise:format:]
ดังต่อไปนี้:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
คำเตือนที่นี่ ใน Objective-C ซึ่งแตกต่างจากภาษาอื่นที่คล้ายคลึงกันโดยทั่วไปคุณควรพยายามหลีกเลี่ยงการใช้ข้อยกเว้นสำหรับสถานการณ์ข้อผิดพลาดทั่วไปที่อาจเกิดขึ้นในการทำงานปกติ
เอกสารประกอบของ Apple สำหรับ Obj-C 2.0ระบุสิ่งต่อไปนี้: "สำคัญ: ข้อยกเว้นมีทรัพยากรจำนวนมากใน Objective-C คุณไม่ควรใช้ข้อยกเว้นสำหรับการควบคุมการไหลทั่วไปหรือเพียงเพื่อบ่งบอกถึงข้อผิดพลาด (เช่นไฟล์
เอกสารการจัดการข้อยกเว้นทางแนวคิดของ Appleอธิบายเหมือนกัน แต่มีคำเพิ่มเติม: "สำคัญ: คุณควรสำรองการใช้ข้อยกเว้นสำหรับการเขียนโปรแกรมหรือข้อผิดพลาดรันไทม์ที่ไม่คาดคิดเช่นการเข้าถึงคอลเลกชันที่ไม่อยู่ในขอบเขต และสูญเสียการเชื่อมต่อกับเซิร์ฟเวอร์หน้าต่างคุณมักจะดูแลข้อผิดพลาดประเภทต่าง ๆ โดยมีข้อยกเว้นเมื่อมีการสร้างแอปพลิเคชันมากกว่าที่รันไทม์ [..... ] แทนที่จะเป็นข้อยกเว้นวัตถุข้อผิดพลาด (NSError) และ กลไกการส่งมอบข้อผิดพลาดโกโก้เป็นวิธีที่แนะนำในการสื่อสารข้อผิดพลาดที่คาดหวังในแอปพลิเคชัน Cocoa "
เหตุผลนี้ส่วนหนึ่งเป็นไปตามการเขียนโปรแกรมสำนวนใน Objective-C (ใช้ค่าตอบแทนในกรณีที่ง่ายและพารามิเตอร์อ้างอิงโดย (มักจะคลาส NSError) ในกรณีที่ซับซ้อนมากขึ้น) ส่วนที่โยนและจับข้อยกเว้นมีราคาแพงมากและ ในที่สุด (และ perpaps ที่สำคัญที่สุด) ว่าข้อยกเว้น Objective-C เป็น wrapper บาง ๆ รอบฟังก์ชั่น setjmp () และ longjmp () ของ C โดยพื้นฐานแล้วทำให้การจัดการหน่วยความจำที่ระมัดระวังดูที่คำอธิบายนี้
@throw([NSException exceptionWith…])
Xcode รับรู้@throw
คำสั่งเป็นฟังก์ชั่นจุดออกเช่นreturn
คำสั่ง การใช้@throw
ไวยากรณ์หลีกเลี่ยงความผิดพลาด " การควบคุมอาจจะถึงจุดสิ้นสุดของฟังก์ชั่นที่ไม่เป็นโมฆะ " [NSException raise:…]
คำเตือนว่าคุณอาจได้รับจาก
นอกจากนี้ยัง@throw
สามารถใช้ในการโยนวัตถุที่ไม่ได้อยู่ในคลาส NSException
[NSException raise:format:]
เกี่ยวกับ สำหรับผู้ที่มาจากพื้นหลัง Java คุณจะจำได้ว่า Java แตกต่างระหว่างข้อยกเว้นและ RuntimeException ข้อยกเว้นเป็นข้อยกเว้นที่ตรวจสอบและ RuntimeException ไม่ถูกตรวจสอบ โดยเฉพาะอย่างยิ่ง Java แนะนำให้ใช้ข้อยกเว้นที่ตรวจสอบแล้วสำหรับ "เงื่อนไขข้อผิดพลาดปกติ" และข้อยกเว้นที่ไม่ได้ตรวจสอบสำหรับ "ข้อผิดพลาดรันไทม์ที่เกิดจากข้อผิดพลาดของโปรแกรมเมอร์" ดูเหมือนว่าควรใช้ข้อยกเว้น Objective-C ในสถานที่เดียวกันกับที่คุณจะใช้ข้อยกเว้นที่ไม่ได้ตรวจสอบและควรใช้ค่าส่งคืนรหัสข้อผิดพลาดหรือค่า NSError ในสถานที่ที่คุณจะใช้ข้อยกเว้นที่ตรวจสอบ
ฉันคิดว่าจะสอดคล้องกันดีกว่าที่จะใช้ @throw กับคลาสของคุณเองที่ขยาย NSException จากนั้นคุณใช้สัญลักษณ์เดียวกันเพื่อลองจับในที่สุด:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple อธิบายวิธีโยนและจัดการข้อยกเว้นที่นี่: การ จับข้อยกเว้นการ โยนข้อยกเว้น
เนื่องจาก ObjC 2.0 ข้อยกเว้น Objective-C ไม่ใช่กระดาษห่อสำหรับ setjmp () longjmp () ของ C อีกต่อไปและเข้ากันได้กับข้อยกเว้น C ++, @try นั้น "ไม่มีค่าใช้จ่าย" แต่การโยนและจับข้อยกเว้นมีราคาแพงกว่า
อย่างไรก็ตามการยืนยัน (การใช้ NSAssert และ NSCAssert macro family) จะเป็นการโยน NSException และมีเหตุผลที่จะใช้มันเป็นสถานะ Ries
ใช้ NSError เพื่อสื่อสารความล้มเหลวมากกว่าข้อยกเว้น
จุดด่วนเกี่ยวกับ NSError:
NSError อนุญาตให้รหัสข้อผิดพลาดสไตล์ C (จำนวนเต็ม) ระบุสาเหตุที่ชัดเจนและหวังว่าจะช่วยให้ตัวจัดการข้อผิดพลาดสามารถเอาชนะข้อผิดพลาดได้ คุณสามารถตัดรหัสข้อผิดพลาดจากไลบรารี C เช่น SQLite ในอินสแตนซ์ NSError ได้อย่างง่ายดาย
NSError ยังมีประโยชน์ในการเป็นวัตถุและเสนอวิธีการอธิบายข้อผิดพลาดในรายละเอียดเพิ่มเติมกับสมาชิกพจนานุกรม userInfo
แต่ที่ดีที่สุดคือ NSError ไม่สามารถถูกโยนได้ดังนั้นจึงส่งเสริมวิธีการจัดการข้อผิดพลาดเชิงรุกมากกว่าภาษาอื่น ๆ ที่เพียงแค่โยนมันฝรั่งร้อนไปไกลกว่านั้น ไม่ได้รับการจัดการด้วยวิธีที่มีความหมายใด ๆ (ไม่ใช่หากคุณเชื่อในการติดตามหลักการที่ใหญ่ที่สุดของ OOP ในการซ่อนข้อมูลนั่นคือ)
ลิงค์อ้างอิง : การอ้างอิง
นี่คือวิธีที่ฉันเรียนรู้จาก "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
แต่ก็ไม่ได้บอกมากเกี่ยวกับ :)
คุณสามารถใช้สองวิธีในการเพิ่มข้อยกเว้นในลอง catch catch block
@throw[NSException exceptionWithName];
หรือวิธีที่สอง
NSException e;
[e raise];
ฉันเชื่อว่าคุณไม่ควรใช้ข้อยกเว้นเพื่อควบคุมโฟลว์โปรแกรมปกติ แต่ข้อยกเว้นควรถูกโยนทิ้งเมื่อใดก็ตามที่ค่าบางค่าไม่ตรงกับค่าที่ต้องการ
ตัวอย่างเช่นถ้าฟังก์ชั่นบางอย่างยอมรับค่าและค่านั้นไม่เคยได้รับอนุญาตให้เป็นศูนย์ดังนั้นจึงเป็นเรื่องดีที่จะต้องยกเว้นข้อยกเว้นจากนั้นพยายามทำบางสิ่งที่ 'ฉลาด' ...
Ries
คุณควรโยนข้อยกเว้นถ้าคุณพบว่าตัวเองอยู่ในสถานการณ์ที่บ่งบอกถึงข้อผิดพลาดการเขียนโปรแกรมและต้องการที่จะหยุดการทำงานของแอปพลิเคชัน ดังนั้นวิธีที่ดีที่สุดในการโยนข้อยกเว้นคือใช้มาโคร NSAssert และ NSParameterAssert และตรวจสอบให้แน่ใจว่าไม่ได้กำหนด NS_BLOCK_ASSERTIONS
โค้ดตัวอย่างสำหรับกรณี: @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
}
}
ไม่มีเหตุผลที่จะไม่ใช้ข้อยกเว้นตามปกติในวัตถุประสงค์ 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 ตัวหนึ่งก็ตาม
@throw([NSException exceptionWith…])
เพราะมันกระชับกว่า