Làm cách nào tôi có thể sử dụng NSError trong Ứng dụng iPhone của mình?


228

Tôi đang làm việc để bắt lỗi trong ứng dụng của mình và tôi đang xem xét sử dụng NSError. Tôi hơi bối rối về cách sử dụng nó, và làm thế nào để đưa nó vào.

Ai đó có thể cung cấp một ví dụ về cách tôi cư trú sau đó sử dụng NSError?

Câu trả lời:


473

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 NSErrorcon trỏ. Nếu một cái gì đó thực sự sai trong phương thức đó, tôi có thể điền NSErrortham 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 localizedDescriptionvì 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 .


37
đây là ví dụ hài hước nhất từ ​​trước đến nay
ming yeow

đây là một câu trả lời khá tuyệt vời, mặc dù có một số vấn đề trong ARC và idchuyển sang a BOOL. Bất kỳ biến thể tương thích ARC nhỏ sẽ được nhiều đánh giá cao.
NSTJ

6
@TomJowett Tôi thực sự bực mình nếu cuối cùng chúng tôi không thể chấm dứt nạn đói thế giới chỉ vì Apple thúc đẩy chúng tôi chuyển sang thế giới chỉ ARC mới hơn.
Manav

1
loại trả về có thể được BOOL. Trả về NOtrong 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 != nilxử lý nó.
Gabriele Petronella

8
-1: Bạn thực sự cần kết hợp mã xác minh **errorkhô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.
FreeAsInBeer

58

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 1002có 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 stringstậ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];

9
Câu trả lời chính xác! Nhưng tại sao không đặt mô tả được bản địa hóa trong từ điển thông tin người dùng nơi nó thuộc về? [Lỗi NSErrorWithDomain: FSMyAppErrorDomain mã: FSProfileParsingFailedError userInfo: @ {NSLocalizedDescripKey: FS_ERROR_LOCALIZED_DESCRIPTION (error.code)}];
Richard Venable

1
Có nơi nào đặc biệt mà tôi nên đặt tệp chuỗi không? Từ FS_ERROR_LOCALIZED_DESCRIPTION () Tôi chỉ nhận được số (mã lỗi).
huggie

@huggie: không thực sự chắc chắn về ý của bạn. Tôi thường đặt các macro này mà tôi sử dụng trên toàn bộ ứng dụng trong một tệp được gọi Constants+Macros.hvà nhập tệp này trong tiêu đề tiền tố ( .pchtệ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ừ inttới NSStringlà không thực sự cần thiết, mặc dù tôi đã không kiểm tra này.
Wolfgang Schreurs

@huggie: ow, tôi nghĩ bây giờ tôi đã hiểu bạn. Các chuỗi phải ở trong một tệp ( .stringstệ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 NSLocalizedStringFromTabletại đây: developer.apple.com/l
Library / mac / document / cocoa / conceptionual / khác

1
@huggie: Vâng, tôi đã sử dụng các bảng chuỗi được bản địa hóa. Mã trong macro FS_ERROR_LOCALIZED_DESCRIPTIONkiể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 .stringscác tệp nếu điều này xa lạ với bạn.
Wolfgang Schreurs

38

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

30

Mục tiêu-C

NSError *err = [NSError errorWithDomain:@"some_domain"
                                   code:100
                               userInfo:@{
                                           NSLocalizedDescriptionKey:@"Something went wrong"
                               }];

Swift 3

let error = NSError(domain: "some_domain",
                      code: 100,
                  userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])

9

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


3

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 BOOLkhô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 errorcó 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.

3
@Gabriela: Apple tuyên bố rằng khi sử dụng các biến không xác định để trả về lỗi, bản thân phương thức phải luôn có một số giá trị trả về trong trường hợp thành công hoặc thất bại. Apple kêu gọi các nhà phát triển trước tiên kiểm tra giá trị trả về và chỉ khi giá trị trả về bằng cách nào đó kiểm tra lỗi không hợp lệ. Xem trang sau: developer.apple.com/l
Library / mac / # document / Cocoa / Conceptionual / khác

3

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");
    }
}];

0

Vâng, đó là một chút ngoài phạm vi câu hỏi nhưng trong trường hợp bạn không có tùy chọn cho NSError, bạn luôn có thể hiển thị lỗi cấp thấp:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);

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