Câu trả lời:
Chà, những gì tôi thường làm là có các phương thức của tôi có thể bị lỗi trong thời gian chạy lấy tham chiếu đến một NSError
con trỏ. Nếu một cái gì đó thực sự sai trong phương thức đó, tôi có thể điền NSError
tham chiếu với dữ liệu lỗi và trả về nil từ phương thức.
Thí dụ:
- (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;
}
Sau đó chúng ta có thể sử dụng phương pháp như thế này. Thậm chí đừng bận tâm kiểm tra đối tượng lỗi trừ khi phương thức trả về nil:
// 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.
Chúng tôi có thể truy cập lỗi localizedDescription
vì chúng tôi đặt giá trị cho NSLocalizedDescriptionKey
.
Nơi tốt nhất để biết thêm thông tin là tài liệu của Apple . Nó thực sự là tốt.
Ngoài ra còn có một hướng dẫn đơn giản, hay về Cốc Cốc là bạn gái của tôi .
id
chuyển sang a BOOL
. Bất kỳ biến thể tương thích ARC nhỏ sẽ được nhiều đánh giá cao.
BOOL
. Trả về NO
trong trường hợp có lỗi và thay vì kiểm tra giá trị trả về, chỉ cần kiểm tra error
. Nếu nil
đi trước, nếu != nil
xử lý nó.
**error
không phải là không. Nếu không, chương trình sẽ đưa ra một lỗi hoàn toàn không thân thiện và không làm cho nó rõ ràng những gì đang xảy ra.
Tôi muốn thêm một số đề xuất dựa trên việc thực hiện gần đây nhất của tôi. Tôi đã xem một số mã từ Apple và tôi nghĩ rằng mã của tôi hoạt động theo cùng một cách.
Các bài viết ở trên đã giải thích cách tạo các đối tượng NSError và trả lại chúng, vì vậy tôi sẽ không bận tâm với phần đó. Tôi sẽ chỉ cố gắng đề xuất một cách tốt để tích hợp các lỗi (mã, tin nhắn) trong ứng dụng của riêng bạn.
Tôi khuyên bạn nên tạo 1 tiêu đề sẽ là tổng quan về tất cả các lỗi trong miền của bạn (ví dụ: ứng dụng, thư viện, v.v.). Tiêu đề hiện tại của tôi trông như thế này:
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";
Bây giờ khi sử dụng các giá trị trên cho các lỗi, Apple sẽ tạo một số thông báo lỗi tiêu chuẩn cơ bản cho ứng dụng của bạn. Một lỗi có thể được tạo như sau:
+ (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;
}
Thông báo lỗi do Apple tạo ( error.localizedDescription
) cho mã trên sẽ giống như sau:
Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"
Ở trên đã khá hữu ích cho nhà phát triển, vì thông báo hiển thị tên miền xảy ra lỗi và mã lỗi tương ứng. Người dùng cuối sẽ không biết mã lỗi 1002
có nghĩa là gì, vì vậy bây giờ chúng ta cần triển khai một số thông báo hay cho mỗi mã.
Đối với các thông báo lỗi, chúng tôi phải ghi nhớ nội địa hóa (ngay cả khi chúng tôi không triển khai các thông báo được bản địa hóa ngay lập tức). Tôi đã sử dụng cách tiếp cận sau đây trong dự án hiện tại của mình:
1) tạo một strings
tập tin sẽ chứa các lỗi. Các tập tin chuỗi có thể dễ dàng định vị. Các tập tin có thể trông như sau:
FSError.strings
"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."
2) Thêm macro để chuyển đổi mã số nguyên thành thông báo lỗi cục bộ. Tôi đã sử dụng 2 macro trong tệp Hằng số + Macros.h của mình. Tôi luôn bao gồm tệp này trong tiêu đề tiền tố ( MyApp-Prefix.pch
) để thuận tiện.
Hằng số + 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) Bây giờ thật dễ dàng để hiển thị thông báo lỗi thân thiện với người dùng dựa trên mã lỗi. Một ví dụ:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code)
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
Constants+Macros.h
và nhập tệp này trong tiêu đề tiền tố ( .pch
tệp) để nó có sẵn ở mọi nơi. Nếu bạn có nghĩa là bạn chỉ sử dụng 1 trong 2 macro, điều đó có thể hoạt động. Có lẽ việc chuyển đổi từ int
tới NSString
là không thực sự cần thiết, mặc dù tôi đã không kiểm tra này.
.strings
tệp) có thể bản địa hóa, vì đó là nơi macro của Apple sẽ nhìn. Đọc về cách sử dụng NSLocalizedStringFromTable
tại đây: developer.apple.com/l
FS_ERROR_LOCALIZED_DESCRIPTION
kiểm tra chuỗi có thể bản địa hóa trong một tệp có tên FSError.strings
. Bạn có thể muốn xem hướng dẫn bản địa hóa của Apple trên .strings
các tệp nếu điều này xa lạ với bạn.
Alex trả lời tuyệt vời. Một vấn đề tiềm năng là sự trung thành của NULL. Tài liệu tham khảo của Apple về Tạo và trả lại các đối tượng 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;
...
Vui lòng tham khảo hướng dẫn sau đây
tôi hy vọng nó sẽ hữu ích cho bạn nhưng trước khi bạn phải đọc tài liệu của NSError
Đây là liên kết rất thú vị tôi tìm thấy gần đây ErrorHandling
Tôi sẽ thử tóm tắt câu trả lời tuyệt vời của Alex và quan điểm của jlmendezbonini, thêm một sửa đổi sẽ làm cho mọi thứ tương thích với ARC (cho đến nay không phải vì ARC sẽ phàn nàn vì bạn nên trả lại id
, có nghĩa là "bất kỳ đối tượng nào", nhưng BOOL
không phải là một đối tượng kiểu).
- (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;
}
Bây giờ thay vì kiểm tra giá trị trả về của lệnh gọi phương thức của chúng tôi, chúng tôi kiểm tra xem error
có còn không nil
. Nếu không phải là chúng ta có vấn đề.
// 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.
Một mẫu thiết kế khác mà tôi đã thấy liên quan đến việc sử dụng các khối, đặc biệt hữu ích khi một phương thức đang được chạy không đồng bộ.
Giả sử chúng tôi có các mã lỗi sau được xác định:
typedef NS_ENUM(NSInteger, MyErrorCodes) {
MyErrorCodesEmptyString = 500,
MyErrorCodesInvalidURL,
MyErrorCodesUnableToReachHost,
};
Bạn sẽ xác định phương thức của mình có thể gây ra lỗi như vậy:
- (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);
}
}
Và sau đó khi bạn gọi nó, bạn không cần phải lo lắng về việc khai báo đối tượng NSError (hoàn thành mã sẽ làm điều đó cho bạn) hoặc kiểm tra giá trị trả về. Bạn chỉ có thể cung cấp hai khối: một khối sẽ được gọi khi có ngoại lệ và một khối được gọi khi thành công:
[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");
}
}];
extension NSError {
static func defaultError() -> NSError {
return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
}
}
mà tôi có thể sử dụng NSError.defaultError()
bất cứ khi nào tôi không có đối tượng lỗi hợp lệ.
let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.