您的位置:首页 > 移动开发 > Objective-C

Effective Objective-C 2.0: Item 21: Understand the Objective-C Error Model

2013-12-07 19:05 429 查看


Item 21: Understand the Objective-C Error Model

Many modern languages, including Objective-C, have exceptions. If you have come from a Java background, you’ll most likely be accustomed to using exceptions to handle error cases. If you’re
used to using exceptions for this task, you are going to have to go back and forget everything you knew about exceptions and start again.

The first thing to note is that Automatic Reference Counting (ARC, see Item
30) is not exception safe by default. In practice, this means that any objects that should be released at the end of a scope in which
an exception is thrown will not be released. It is possible to turn on a compiler flag to enable exception-safe code to be generated, but it introduces extra code that has to be run even for the scenario in which no exception is thrown. The compiler
flag that turns this on is
-fobjc-arc-exceptions
.

Even when not using ARC, it is difficult to write code that is safe against memory leaks when exceptions are used. Suppose that a resource is created and needs to be released once it is no longer
needed. If an exception is thrown before the resource has been released, that release will never be done:

Click here to view code image

id someResource = /* ... */;

if ( /* check for error */ ) {

@throw [NSException exceptionWithName:@"ExceptionName"

reason:@"There was an error"

userInfo:nil];

}

[someResource doSomething];

[someResource release];

Of course, the way to solve this problem is to release
someResource
before throwing; however, if there are
many resources to release and more complicated code paths, the code easily becomes cluttered. Also, if something is added to such code, it would be easy to forget to add releases before all times an exception is thrown.

Objective-C has taken the approach recently to save exceptions for the rare scenario in which recovery should be
avoided and an exception should cause an application to exit. This means that complex exception-safe code does not need to be involved.

Remembering that exceptions are to be used for fatal errors only, an example of a scenario in which you should consider throwing an exception in your own classes is when creating an abstract
base class that should be subclassed before being used.Objective-C has no language construct to say that a class is abstract, unlike some other languages. So
the best way to achieve a similar effect is to throw an exception in any method that must be overridden in subclasses. Anyone who then tries to create an instance of the abstract base class and use it will get an exception thrown:

Click here to view code image

- (void)mustOverrideMethod {

NSString *reason = [NSString stringWithFormat:

@"%@ must be overridden",

NSStringFromSelector(_cmd)];

@throw [NSException

exceptionWithName:NSInternalInconsistencyException

reason:reason

userInfo:nil];

}

But if exceptions are to be used only for fatal errors, what about other errors? The paradigm chosen by Objective-C
to indicate nonfatal errors is either to return nil / 0 from methods where an error has occurred or to use
NSError
.
An example of returning nil / 0 is when in an initializer and an instance cannot be initialized with the parameters passed in:

Click here to view code image

- (id)initWithValue:(id)value {

if ((self = [super init]))
{

if ( /* Value means instance can't be created */ )
{

self = nil;

} else {

// Initialize instance

}

}

return self;

}

In this scenario, if the
if
statement determines that the instance can’t be created with the value passed in—maybe
value
needs
to be non-nil itself—
self
is set to
nil
, and this is what will be returned. A caller of the initializer will understand that
there has been an error, because no instance will have been created.

Using
NSError
provides much more flexibility because it enables
a reason to be given back as to what the error is. An
NSError
object encapsulates three pieces of information:


Error domain (String)

The domain in which the error occurred. This is usually a global variable that can be used to uniquely define the source of the error. The URL-handling subsystem uses the domain
NSURLErrorDomain,
for
example, for all errors that come from parsing or obtaining data from URLs.


Error code (Integer)

A code that uniquely defines within a certain error domain what error has occurred. Often, an
enum
is
used to define the set of errors that can occur within a certain error domain. HTTP requests that fail might use the HTTP status code for this value, for example.


User info (Dictionary)

Extra information about the error, such as a localized description and another error representing the
error that caused this error to occur, to allow information about chains of errors to be represented.

The first way in which errors are commonly used in API design is through the use of delegate protocols. When
an error occurs, an object may pass its delegate the error through one of the protocol’s methods. For example,
NSURLConnection
includes
the following method as part of its delegate protocol,
NSURLConnectionDelegate
:

Click here to view code image

- (void)connection:(NSURLConnection *)connection

didFailWithError:(NSError *)error

When a connection decides that there is an error, such as the connection to the remote server times out, this method is called handing an error representing what happened. This
delegate method doesn’t have to be implemented, so it is up to the user of the
NSURLConnection
class to decide it is necessary to know about the error. This is
preferable to throwing an exception, since it’s left up to the user to decide whether to be told about the error.

The other common way in which
NSError
is
used is through an out-parameter passed to a method. It looks like this:

- (BOOL)doSomething:(NSError**)error

The error variable being passed to the method is a pointer to a pointer to an
NSError
. Or you can think of it
as a pointer to an
NSError
object. This enables the method to, in effect, return an
NSError
object in addition to its return
value. It’s used like this:

Click here to view code image

NSError *error = nil;

BOOL ret = [object doSomething:&error];

if (error) {

// There was an error

}

Often, methods that return an error like this also return a Boolean to indicate success or failure so that you can check the Boolean if you don’t care about what the precise error was or,
if you do care, you can check the returned error. The error parameter can also be
nil
when you don’t care what the returned error is. For instance, you might use the method
like this:

Click here to view
code image

BOOL ret = [object doSomething:nil];

if (ret) {

// There was an error

}

In reality, when using ARC, the compiler translates the
NSError**
in
the method signature to
NSError*__autoreleasing*;
this
means that the object pointed to will be autoreleased at the end of the method. The object has to do this because the
doSomething:
method cannot guarantee that the
caller will be able to release the
NSError
object it created and therefore must add in an
autorelease
.
This gives the same semantics as return values from most methods (excluding, of course, methods that begin with
new
,
alloc
,
copy,
and
mutableCopy
).

The method passes back the error through the out-parameter like this:

Click here to view code image

- (BOOL)doSomething:(NSError**)error {

// Do something that may cause an error

if ( /* there was an error */ ) {

if (error) {

// Pass the 'error' through the out-parameter

*error = [NSError errorWithDomain:domain

code:code

userInfo:userInfo];

}

return NO; ///< Indicate
failure

} else {

return YES; ///< Indicate
success

}

}

The
error
parameter is dereferenced using the
*error
syntax, meaning that the value pointed to by
error
is
set to the new
NSError
object. The
error
parameter
must first be checked to see whether it is non-nil, since dereferencing the null pointer will result in a segmentation fault and cause a crash. Since it is fine for a caller to pass nil, this check must be made for the case that it doesn’t care about
the error,

The domain, code, and user information portions of the error object should be set to something that makes sense for the error that has happened. This enables the caller to behave differently,
depending on the type of error that has occurred. The domain is best defined as a global constant
NSString,
and
the error codes are best defined as an enumeration type. For example, you might define them like this:

Click here to view
code image

// EOCErrors.h

extern NSString *const EOCErrorDomain;

typedef NS_ENUM(NSUInteger, EOCError)
{

EOCErrorUnknown = –1,

EOCErrorInternalInconsistency = 100,

EOCErrorGeneralFault = 105,

EOCErrorBadInput = 500,

};

// EOCErrors.m

NSString *const EOCErrorDomain = @"EOCErrorDomain";

Creating an error domain for your library is prudent, since it allows you to create and return
NSError
objects
that consumers can ascertain came from your library. Creating an enumeration type for the error codes is also a good idea, since it documents the errors and gives the codes a meaningful name. You may also decide to comment the header file where they are defined
with even more detailed descriptions of each error type.


Things to Remember


Use exceptions
only for fatal errors that should bring down the entire application.


For nonfatal
errors, either provide a delegate method to handle errors or offer an out-parameter
NSError
object.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐