ném một ngoại lệ trong mục tiêu-c / ca cao


Câu trả lời:


528

Tôi sử dụng [NSException raise:format:]như sau:

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

9
Tôi thích cách này như được áp dụng cho @throw([NSException exceptionWith…])cách tiếp cận vì nó ngắn gọn hơn.
Sam Soffes

9
Hãy chắc chắn đọc các cảnh báo quan trọng từ các tác hại ( stackoverflow.com/questions/324284/324805#324805 )
e.James

26
Tôi thường thích điều này quá, nhưng có một gotcha. Có thể chỉ là phiên bản Xcode hiện tại của tôi, nhưng cú pháp [NSException nâng cao ...] dường như không được trình phân tích cú pháp nhận ra là đường dẫn thoát khỏi phương thức trả về giá trị. Tôi đang thấy cảnh báo "Điều khiển có thể đạt đến cuối hàm không trống" khi sử dụng cú pháp này, nhưng với cú pháp @throw ([NSException ngoại lệ vớiithith]], trình phân tích cú pháp nhận ra đó là một lối thoát và không hiển thị cảnh báo.
mattorb

1
@mpstx Tôi luôn sử dụng cú pháp ném vì lý do bạn đã đưa ra (vẫn còn liên quan hai năm sau trong Xcode 4.6, và có lẽ sẽ luôn như vậy). Có IDE nhận ra rằng việc ném một ngoại lệ là một điểm thoát chức năng thường xuyên là vấn đề nếu bạn muốn tránh các cảnh báo.
Đánh dấu Amery

FWIW Tôi nhận thấy rằng các khối @ try / @ bắt cũng dẫn đến âm tính giả cho cảnh báo "kiểm soát đến hết chức năng không trống" (nghĩa là cảnh báo không được hiển thị khi cần)
Brian Gerstle

256

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 .


11
Tôi nghĩ điều này áp dụng cho hầu hết các ngôn ngữ lập trình: "cố gắng tránh sử dụng ngoại lệ cho các tình huống lỗi phổ biến". Áp dụng tương tự trong Java; đó là thực tế xấu để xử lý lỗi đầu vào của người dùng (ví dụ) với các ngoại lệ. Không chỉ vì sử dụng tài nguyên, mà còn cho sự rõ ràng về mã.
beetstra

6
Quan trọng hơn, Ngoại lệ trong Ca cao được thiết kế để chỉ ra các lỗi chương trình không thể phục hồi. Làm khác là chạy ngược lại khuôn khổ và có thể dẫn đến hành vi không xác định. Xem stackoverflow.com/questions/3378696/iphone-try-end-try/ cho để biết chi tiết.
KPM

9
"Áp dụng tương tự trong Java;" Không đồng ý. Bạn có thể sử dụng các ngoại lệ được kiểm tra trong Java chỉ tốt cho các điều kiện lỗi thông thường. Tất nhiên bạn sẽ không sử dụng ngoại lệ Runtime.
Daniel Ryan

Tôi thích cách sử dụng Cacao (ngoại lệ chỉ dành cho lỗi lập trình viên) vì vậy tôi cũng thích làm điều đó hơn trong Java, nhưng thực tế là bạn nên thực hiện theo các thực tiễn điển hình trong môi trường và ngoại lệ cho việc xử lý lỗi giống như một mùi hôi trong Objective-C, nhưng được sử dụng rất nhiều cho mục đích đó trong Java.
gnasher729

1
Nhận xét này không trả lời câu hỏi. Có lẽ OP chỉ muốn đánh sập ứng dụng để kiểm tra xem khung báo cáo sự cố có hoạt động như mong đợi hay không.
Simon

62
@throw([NSException exceptionWith…])

Xcode nhận ra các @throwcâu lệnh là các điểm thoát chức năng, giống như các returncâu lệnh. Sử dụng @throwcú 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, @throwcó thể được sử dụng để ném các đối tượng không thuộc lớp NSException.


11
@Steph Thirion: Xem developer.apple.com/documentation/Cocoa/Conceptual/Exceptions/ Kẻ để biết tất cả các chi tiết. Dòng dưới cùng? Cả hai sẽ hoạt động, nhưng @throw có thể được sử dụng để ném các đối tượng không thuộc lớp NSException.
e.James

33

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.


1
Có, điều này đúng (sau 4 năm: D), tạo lớp lỗi ABCError của riêng bạn, mở rộng từ lớp NSError và sử dụng nó cho các trường hợp ngoại lệ được kiểm tra thay vì NSExceptions. Tăng NSExceptions khi xảy ra lỗi lập trình viên (tình huống không mong muốn như sự cố định dạng số).
chathuram

15

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ệ


tôi vẫn gặp sự cố do ngoại lệ thời gian chạy trong khối thử
famfamfam

14

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.


Tốt để biết! Chúng tôi có một thư viện bên thứ ba mà chúng tôi không muốn sửa đổi, đưa ra các ngoại lệ cho những lỗi nhỏ nhất. Chúng tôi phải bắt chúng ở một nơi trong ứng dụng và nó chỉ khiến chúng tôi co rúm lại, nhưng điều này khiến tôi cảm thấy tốt hơn một chút.
Yuri Brigance

8

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).

Liên kết tham khảo : Tham khảo


Nhận xét này không trả lời câu hỏi. Có lẽ OP chỉ muốn đánh sập ứng dụng để kiểm tra xem khung báo cáo sự cố có hoạt động như mong đợi hay không.
Simon

7

Đây là cách tôi học được từ "The Big Nerd Ranch Guide (ấn bản thứ 4)":

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

OK, nhưng nó không nói nhiều về userInfo:nil. :)
Cœur

6

Bạn có thể sử dụng hai phương pháp để tăng ngoại lệ trong khối thử bắt

@throw[NSException exceptionWithName];

hoặc phương pháp thứ hai

NSException e;
[e raise];

3

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


0

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.


0

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

}

}


-7

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.


4
Tôi khuyên bạn nên cố gắng lập trình một máy chủ có ngoại lệ như là một phần của dòng lỗi thông thường trước khi đưa ra các tuyên bố khái quát như "không có lý do gì để không sử dụng ngoại lệ thông thường trong mục tiêu C". Dù bạn có tin hay không, có những lý do để viết các ứng dụng hiệu suất cao (hoặc ít nhất là một phần của các ứng dụng) trong ObjC và ném các ngoại lệ thường làm cản trở nghiêm trọng hiệu suất.
jbenet

6
Thực sự có những lý do rất tốt tại sao không sử dụng ngoại lệ trong ca cao. Xem câu trả lời của Bill Bumgarner tại đây để biết thêm: stackoverflow.com/questions/3378696/iphone-try-end-try/ ,. Anh ta biết những gì anh ta nói về (gợi ý: kiểm tra chủ nhân của anh ta). Các ngoại lệ trong Ca cao được coi là lỗi không thể phục hồi và có thể khiến hệ thống ở trạng thái không ổn định. NSError là cách để vượt qua các lỗi chung.
Brad Larson

Ngoại lệ là đặc biệt . Thất bại trong quy tắc kinh doanh chắc chắn không đủ điều kiện. "Tìm kiếm và thiết kế mã ngoại lệ nặng có thể mang lại một chiến thắng hoàn hảo." MSDN qua codinghorror.com/blog/2004/10/...
Jonathan Watmough

3
Ngoại lệ không thể được ném từ các khối. Các ngoại lệ được ném trong môi trường ARC có thể làm cho chương trình của bạn bị rò rỉ. Do đó, downvote.
Moszi

"Không quan trọng việc ném và bắt một ngoại lệ đắt đỏ như thế nào" Tôi đang viết một trình giả lập trong đó hiệu suất là rất quan trọng. Tôi không thể ném cả đống ngoại lệ đắt tiền.
NobodyNada 8/2/2015
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.