如何在我的 iPhone 应用程序中使用 NSError?

发布于 2024-10-11 05:41:10 字数 144 浏览 6 评论 0原文

我正在努力捕获应用程序中的错误,并且正在考虑使用 NSError。我对如何使用它以及如何填充它有点困惑。

有人可以提供一个关于如何填充然后使用 NSError 的示例吗?

I am working on catching errors in my app, and I am looking into using NSError. I am slightly confused about how to use it, and how to populate it.

Could someone provide an example on how I populate then use NSError?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

胡渣熟男 2024-10-18 05:41:10

嗯,我通常做的是让我的方法在运行时可能出错,并引用 NSError 指针。如果该方法中确实出现问题,我可以使用错误数据填充 NSError 引用,并从该方法返回 nil。

示例:

- (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;
}

然后我们可以使用这样的方法。除非该方法返回 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.

我们能够访问错误的 localizedDescription 因为我们为 NSLocalizedDescriptionKey 设置了一个值。

获取更多信息的最佳位置是 Apple 文档。确实很好。

可可是我的女朋友

Well, what I usually do is have my methods that could error-out at runtime take a reference to a NSError pointer. If something does indeed go wrong in that method, I can populate the NSError reference with error data and return nil from the method.

Example:

- (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;
}

We can then use the method like this. Don't even bother to inspect the error object unless the method returns 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.

We were able to access the error's localizedDescription because we set a value for NSLocalizedDescriptionKey.

The best place for more information is Apple's documentation. It really is good.

There is also a nice, simple tutorial on Cocoa Is My Girlfriend.

So要识趣 2024-10-18 05:41:10

我想根据我最近的实施添加一些更多建议。我查看了 Apple 的一些代码,我认为我的代码的行为方式大致相同。

上面的帖子已经解释了如何创建 NSError 对象并返回它们,所以我不会打扰那部分。我只是尝试建议一种在您自己的应用程序中集成错误(代码、消息)的好方法。


我建议创建 1 个标头,它将概述您的域(即应用程序、库等)的所有错误。我当前的标头如下所示:

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

现在,当使用上述错误值时,Apple 将为您的应用程序创建一些基本的标准错误消息。可以创建如下所示的错误:

+ (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;
}

上述代码的标准 Apple 生成的错误消息 (error.localizedDescription) 如下所示:

Error Domain=com.felis.myapp 代码=1002“操作无法完成。(com.felis.myapp错误1002。)”

上面的内容对于开发人员来说已经非常有帮助了,因为该消息显示了发生错误的域以及相应的错误代码。最终用户不知道错误代码 1002 的含义,因此现在我们需要为每个代码实现一些不错的消息。

对于错误消息,我们必须牢记本地化(即使我们不立即实现本地化消息)。我在当前项目中使用了以下方法:


1) 创建一个包含错误的 strings 文件。字符串文件很容易本地化。该文件可能如下所示:

FSError.strings

"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."

2) 添加宏以将整数代码转换为本地化错误消息。我在 Constants+Macros.h 文件中使用了 2 个宏。为了方便起见,我总是将此文件包含在前缀标头 (MyApp-Prefix.pch) 中。

Constants+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) 现在可以轻松地根据错误代码显示用户友好的错误消息。一个例子:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];

I would like to add some more suggestions based on my most recent implementation. I've looked at some code from Apple and I think my code behaves in much the same way.

The posts above already explain how to create NSError objects and return them, so I won't bother with that part. I'll just try to suggest a good way to integrate errors (codes, messages) in your own app.


I recommend creating 1 header that will be an overview of all the errors of your domain (i.e. app, library, etc..). My current header looks like this:

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

Now when using the above values for errors, Apple will create some basic standard error message for your app. An error could be created like the following:

+ (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;
}

The standard Apple-generated error message (error.localizedDescription) for the above code will look like the following:

Error Domain=com.felis.myapp Code=1002 "The operation couldn’t be completed. (com.felis.myapp error 1002.)"

The above is already quite helpful for a developer, since the message displays the domain where the error occured and the corresponding error code. End users will have no clue what error code 1002 means though, so now we need to implement some nice messages for each code.

For the error messages we have to keep localisation in mind (even if we don't implement localized messages right away). I've used the following approach in my current project:


1) create a strings file that will contain the errors. Strings files are easily localizable. The file could look like the following:

FSError.strings

"1000" = "User not logged in.";
"1001" = "Logout failed.";
"1002" = "Parser failed.";
"1003" = "Incorrect username or password.";
"1004" = "Failed to parse FNID."

2) Add macros to convert integer codes to localized error messages. I've used 2 macros in my Constants+Macros.h file. I always include this file in the prefix header (MyApp-Prefix.pch) for convenience.

Constants+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) Now it's easy to show a user friendly error message based on an error code. An example:

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" 
            message:FS_ERROR_LOCALIZED_DESCRIPTION(error.code) 
            delegate:nil 
            cancelButtonTitle:@"OK" 
            otherButtonTitles:nil];
[alert show];
橘虞初梦 2024-10-18 05:41:10

亚历克斯的回答很好。一个潜在的问题是 NULL 取消引用。 Apple 的参考 创建并返回 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;
...

Great answer Alex. One potential issue is the NULL dereference. Apple's reference on Creating and Returning NSError objects

...
[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;
...
狂之美人 2024-10-18 05:41:10

Objective-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"])

Objective-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"])
回心转意 2024-10-18 05:41:10

请参考以下教程

我希望它对您有所帮助,但在此之前您必须阅读 NSError

这是我最近发现的非常有趣的链接 ErrorHandling

Please refer following tutorial

i hope it will helpful for you but prior you have to read documentation of NSError

This is very interesting link i found recently ErrorHandling

感悟人生的甜 2024-10-18 05:41:10

我将尝试总结 Alex 的精彩答案和 jlmendezbonini 的观点,添加一个修改,使所有内容都兼容 ARC(到目前为止,ARC 不会抱怨,因为您应该返回 id,这意味着“任何object”,但 BOOL 不是对象类型)。

- (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;
}

现在,我们不再检查方法调用的返回值,而是检查 error 是否仍为 nil。如果不是,我们就有问题了。

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

I'll try summarize the great answer by Alex and the jlmendezbonini's point, adding a modification that will make everything ARC compatible (so far it's not since ARC will complain since you should return id, which means "any object", but BOOL is not an object type).

- (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;
}

Now instead of checking for the return value of our method call, we check whether error is still nil. If it's not we have a problem.

// 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.
晨曦慕雪 2024-10-18 05:41:10

我见过的另一种设计模式涉及使用块,这在异步运行方法时特别有用。

假设我们定义了以下错误代码:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

您将定义可以引发错误的方法,如下所示:

- (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);
    }
}

然后当您调用它时,您无需担心声明 NSError 对象(代码完成将为您完成) ,或检查返回值。您可以只提供两个块:一个在出现异常时调用,另一个在成功时调用:

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

Another design pattern that I have seen involves using blocks, which is especially useful when a method is being run asynchronously.

Say we have the following error codes defined:

typedef NS_ENUM(NSInteger, MyErrorCodes) {
    MyErrorCodesEmptyString = 500,
    MyErrorCodesInvalidURL,
    MyErrorCodesUnableToReachHost,
};

You would define your method that can raise an error like so:

- (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);
    }
}

And then when you call it, you don't need to worry about declaring the NSError object (code completion will do it for you), or checking the returning value. You can just supply two blocks: one that will get called when there is an exception, and one that gets called when it succeeds:

[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");
    }
}];
筱果果 2024-10-18 05:41:10
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

每当我没有有效的错误对象时,我都可以使用 NSError.defaultError() 。

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
extension NSError {
    static func defaultError() -> NSError {
        return NSError(domain: "com.app.error.domain", code: 0, userInfo: [NSLocalizedDescriptionKey: "Something went wrong."])
    }
}

which I can use NSError.defaultError() whenever I don't have valid error object.

let error = NSError.defaultError()
print(error.localizedDescription) //Something went wrong.
冧九 2024-10-18 05:41:10

好吧,这有点超出范围,但如果你没有 NSError 选项,你可以随时显示低级错误:

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

Well it's a little bit out of question scope but in case you don't have an option for NSError you can always display the Low level error:

 NSLog(@"Error = %@ ",[NSString stringWithUTF8String:strerror(errno)]);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文