Câu trả lời:
Tôi sử dụng [NSException raise:format:]
như sau:
[NSException raise:@"Invalid foo value" format:@"foo of %d is invalid", foo];
Một lời cảnh báo ở đây. Trong Objective-C, không giống như nhiều ngôn ngữ tương tự, bạn thường nên cố gắng tránh sử dụng ngoại lệ cho các tình huống lỗi phổ biến có thể xảy ra trong hoạt động bình thường.
Tài liệu của Apple về Obj-C 2.0 nêu rõ: "Quan trọng: Các ngoại lệ cần nhiều tài nguyên trong Objective-C. Bạn không nên sử dụng ngoại lệ cho kiểm soát luồng chung hoặc đơn giản là để biểu thị các lỗi (chẳng hạn như tệp không thể truy cập được)"
Tài liệu xử lý ngoại lệ theo khái niệm của Apple giải thích tương tự, nhưng có nhiều từ hơn: "Quan trọng: Bạn nên bảo lưu việc sử dụng ngoại lệ để lập trình hoặc các lỗi thời gian chạy không mong muốn như truy cập bộ sưu tập ngoài giới hạn, cố gắng thay đổi các đối tượng bất biến, gửi tin nhắn không hợp lệ và mất kết nối với máy chủ cửa sổ. Bạn thường xử lý các loại lỗi này với các ngoại lệ khi ứng dụng được tạo thay vì trong thời gian chạy. [.....] Thay vì ngoại lệ, các đối tượng lỗi (NSError) và Cơ chế phân phối lỗi ca cao là cách được khuyến nghị để truyền đạt các lỗi dự kiến trong các ứng dụng của Cacao. "
Lý do cho điều này một phần là để tuân thủ các thành ngữ lập trình trong Objective-C (sử dụng các giá trị trả về trong các trường hợp đơn giản và tham số phụ (thường là lớp NSError) trong các trường hợp phức tạp hơn), một phần là ném và bắt ngoại lệ đắt hơn nhiều và cuối cùng (và perpaps quan trọng nhất) rằng các ngoại lệ Objective-C là một trình bao bọc mỏng xung quanh các hàm setjmp () và longjmp () của C, về cơ bản làm rối loạn việc xử lý bộ nhớ cẩn thận của bạn, xem phần giải thích này .
@throw([NSException exceptionWith…])
Xcode nhận ra các @throw
câu lệnh là các điểm thoát chức năng, giống như các return
câu lệnh. Sử dụng @throw
cú pháp để tránh các cảnh báo " Điều khiển có thể đi đến cuối hàm không trống " mà bạn có thể nhận được [NSException raise:…]
.
Ngoài ra, @throw
có thể được sử dụng để ném các đối tượng không thuộc lớp NSException.
Về [NSException raise:format:]
. Đối với những người đến từ nền Java, bạn sẽ nhớ rằng Java phân biệt giữa Exception và RuntimeException. Ngoại lệ là một ngoại lệ được kiểm tra và RuntimeException không được chọn. Cụ thể, Java đề xuất sử dụng các ngoại lệ được kiểm tra cho "điều kiện lỗi thông thường" và các ngoại lệ không được kiểm tra cho "lỗi thời gian chạy do lỗi lập trình viên". Có vẻ như các ngoại lệ Objective-C nên được sử dụng ở cùng một nơi bạn sẽ sử dụng ngoại lệ không được kiểm tra và các giá trị trả về mã lỗi hoặc giá trị NSError được ưu tiên ở những nơi bạn sẽ sử dụng ngoại lệ được kiểm tra.
Tôi nghĩ rằng nhất quán sẽ tốt hơn khi sử dụng @throw với lớp riêng của bạn mở rộng NSException. Sau đó, bạn sử dụng các ký hiệu tương tự để thử bắt cuối cùng:
@try {
.....
}
@catch{
...
}
@finally{
...
}
Apple giải thích ở đây cách ném và xử lý ngoại lệ: Bắt ngoại lệ Ném ngoại lệ
Kể từ ObjC 2.0, các ngoại lệ Objective-C không còn là trình bao bọc cho Cj setjmp () longjmp () và tương thích với ngoại lệ C ++, @try là "miễn phí", nhưng ném và bắt ngoại lệ thì đắt hơn.
Dù sao, các xác nhận (sử dụng họ macro NSAssert và NSCAssert) ném NSException và điều đó thật lành mạnh để sử dụng chúng làm trạng thái Ries.
Sử dụng NSError để liên lạc với các thất bại thay vì ngoại lệ.
Điểm nhanh về NSError:
NSError cho phép mã lỗi kiểu C (số nguyên) xác định rõ nguyên nhân gốc và hy vọng cho phép trình xử lý lỗi khắc phục lỗi. Bạn có thể bọc mã lỗi từ các thư viện C như SQLite trong các trường hợp NSError rất dễ dàng.
NSError cũng có lợi ích là một đối tượng và cung cấp một cách để mô tả lỗi chi tiết hơn với thành viên từ điển userInfo của nó.
Nhưng tốt nhất, NSError KHÔNG THỂ được ném để nó khuyến khích cách tiếp cận chủ động hơn trong việc xử lý lỗi, ngược lại với các ngôn ngữ khác chỉ đơn giản là ném khoai tây nóng hơn và tiếp tục lên ngăn xếp cuộc gọi mà tại đó chỉ có thể báo cáo cho người dùng và không được xử lý theo bất kỳ cách có ý nghĩa nào (không phải nếu bạn tin vào việc tuân theo nguyên lý che giấu thông tin lớn nhất của OOP).
Tôi tin rằng bạn không bao giờ nên sử dụng Ngoại lệ để kiểm soát luồng chương trình bình thường. Nhưng các ngoại lệ nên được ném bất cứ khi nào một số giá trị không khớp với một giá trị mong muốn.
Ví dụ: nếu một số chức năng chấp nhận một giá trị và giá trị đó không bao giờ được phép là không, thì tốt nhất là nên tạo ra một ngoại lệ thay vì cố gắng làm điều gì đó 'thông minh' ...
Ries
Bạn chỉ nên ném ngoại lệ nếu bạn thấy mình trong tình huống chỉ ra lỗi lập trình và muốn ngăn ứng dụng chạy. Do đó, cách tốt nhất để đưa ra các ngoại lệ là sử dụng các macro NSAssert và NSParameterAssert và đảm bảo rằng NS_BLOCK_ASSERTIONS không được xác định.
Mã mẫu cho trường hợp: @throw ([NSException ngoại lệWithName: ...
- (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);
}
}
Sử dụng:
[self parseError:error completionBlock:^(NSString *error) {
NSLog(@"%@", error);
}];
Một trường hợp sử dụng nâng cao hơn:
- (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
}
}
Không có lý do gì để không sử dụng các ngoại lệ thông thường trong mục tiêu C thậm chí để biểu thị các ngoại lệ quy tắc kinh doanh. Apple có thể nói sử dụng NSError, người quan tâm. Obj C đã có từ rất lâu và đã có lúc tài liệu ALL C ++ nói điều tương tự. Lý do không quan trọng việc ném và bắt một ngoại lệ đắt đỏ như thế nào, là thời gian tồn tại của một ngoại lệ cực kỳ ngắn và ... đó là một NGOẠI TRỪ đối với dòng chảy thông thường. Tôi chưa bao giờ nghe ai nói bao giờ trong đời, người đàn ông ngoại lệ đó đã mất một thời gian dài để bị ném và bắt.
Ngoài ra, có những người nghĩ rằng bản thân mục tiêu C quá đắt và thay vào đó là mã trong C hoặc C ++. Vì vậy, nói rằng luôn luôn sử dụng NSError là không sáng suốt và hoang tưởng.
Nhưng câu hỏi của chủ đề này vẫn chưa được trả lời là cách TỐT NHẤT để đưa ra một ngoại lệ. Các cách để trả lại NSError là rõ ràng.
Vậy là: [NSException nâng cao: ... @throw [[NSException alloc] initWithName .... hoặc @throw [[MyCustomException ...?
Tôi sử dụng quy tắc được kiểm tra / không được kiểm tra ở đây hơi khác so với ở trên.
Sự khác biệt thực sự giữa (sử dụng ẩn dụ java ở đây) được kiểm tra / bỏ chọn là rất quan trọng -> bạn có thể phục hồi từ ngoại lệ hay không. Và bằng cách phục hồi tôi có nghĩa là không chỉ KHÔNG sụp đổ.
Vì vậy, tôi sử dụng các lớp ngoại lệ tùy chỉnh với @throw cho các ngoại lệ có thể phục hồi, bởi vì có khả năng tôi sẽ có một số phương thức ứng dụng tìm kiếm một số loại lỗi nhất định trong nhiều khối @catch. Ví dụ: nếu ứng dụng của tôi là máy ATM, tôi sẽ có khối @catch cho "WithdrawalRequestExceedsBalanceException".
Tôi sử dụng NSException: nâng cao cho các ngoại lệ trong thời gian chạy vì tôi không có cách nào để khôi phục ngoại lệ, ngoại trừ bắt nó ở mức cao hơn và ghi nhật ký. Và không có điểm nào trong việc tạo ra một lớp tùy chỉnh cho điều đó.
Dù sao đó là những gì tôi làm, nhưng nếu có một cách diễn đạt tốt hơn, tương tự tôi cũng muốn biết. Trong mã của riêng tôi, vì tôi đã ngừng mã hóa C một thời gian dài trước đây nên tôi không bao giờ trả lại NSError ngay cả khi tôi được API thông qua.
@throw([NSException exceptionWith…])
cách tiếp cận vì nó ngắn gọn hơn.