Objective
Any code will have errors, some of which can be remedied, and some will only crash the Program. Good error handling can improve the robustness of your code and improve the stability of your Program.
Swift version of this article: Swift 3
Objective C
return Nil
If something goes wrong, returning null is a common way of handling objective C. Because in objective c, sending messages to nil is Safe. Like what:
- (instancetype)init
{
Self = [super init];
If (self) {
}
/ / If the initialization fails, will return nil
Return self;
}
Assertion
The assertion specifies the context of our method, and if the assertion is not met, it is crash directly in the debug Environment.
For Example: the Af_resume method in afnetworking
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume];
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
Return Status Code
Return status codes and global error messages are often used Together. The way this error is handled is common in objective C to encapsulate C code, or a pure C method. For example, error handling in Sqlite:
Int result = sqlite3_open(dbPath,&_db );
If(result != SQLITE_OK) {//If an error occurs
}
Another example is when data is written to a file
BOOL succeed = [currentData writeToFile:path atomically:YES];
Nserror
Nserror is the recommended error handling method in Cocoa .
Examples of using nserror to handle errors spread throughout the Cocoatouch Framework.
For Example: Nsfilemanager
NSFileManager * fm = [NSFileManager defaultManager];
NSError * error;
[fm removeItemAtPath:path error:&error];
nsurlsession, for example, passes the error message through Nserror.
[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
A nserror includes the details of the error: there are several main information
- Code Error Status Code
- Domain Error domains
- UserInfo Error Details
For example, a common nsurlerrordomain, that is, a network request failure:
NSURLErrorCancelled = -999,
NSURLErrorBadURL = -1000,
NSURLErrorTimedOut = -1001,
NSURLErrorUnsupportedURL = -1002,
NSURLErrorCannotFindHost = -1003,
//....
NSException
NSException is similar to exceptions in other languages, you can catch exceptions by try-catch, etc.
@try {
//Code that can potentially throw an exception
} @catch (NSException *exception) {
//Handle an exception thrown in the @try block
} @finally {
//Code that gets executed whether or not an exception is thrown
}
of course, You can also throw an exception yourself:
[NSException raise:@"ExceptionName"format:@"Contents"];
- In objective c, it is common for us to throw an exception only if the program is in error and cannot continue Execution.
- Objective C's exception handling is inefficient, usually not used for error handling, and Objective C does not have a similar throw keyword to indicate that a method throws an exception and it is difficult to code to determine if a try-cathe is Required.
Error handling in Swift
Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at Runtime.
Swift provides a complete set of error-throwing-capture-processing Mechanisms. Swift usesErrora protocol to represent the type of error, and Do-try-catch to handle code that might throw an Exception.
Optional
An optional value indicates that a value has either a value or Nil. In swift, Optional is written in enum,
public enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
//...
}
When an error occurs, returning an optional value is a common way to handle it. however, There is one obvious drawback to this approach:
The caller is not sure why it failed, and it is not good to do the related processing.
error protocol and throws
Erroris an empty protocol used to indicate the type of Error.NSErrorandCFErrorboth have followed this agreement.
The throws keyword indicates that this method throws an error and the method caller needs to handle the Error.
In swfit, enumerations are a type of data that is particularly appropriate for handling Error. We first define a class of person to represent people
enum Sex{
case male
case female
}
class Person{
let sex:Sex
var money:CGFloat
init(sex:Sex ,money:CGFloat) {
self.sex = sex
self.money = money
}
}
A person can get married and get married with some mistakes, such as not having enough money, such as gender, and using enumerations to represent Them.
enum MarryError : Error{ case lackMoney case wrongSex}
The implementation of the method is then as Follows:
extension Person{
func marry(with another: Person) throws -> Bool{
guard self.sex != another.sex else{
throw MarryError.wrongSex
}
guard self.money + another.money > 100000 else {
throw MarryError.lackMoney
}
return true
}
}
For a function with the throws keyword, there are two ways to choose when calling:
- Using Do-try-catch code blocks
let tom = Person(sex: .male, money: 100000)
let jack = Person(sex: .male, money: 100000)
do{
try tom.marry(with: jack)
}catch MarryError.wrongSex {
print("Two Person have same sex")
}catch MarryError.lackMoney{
print("Oh, they do not have enough moeny")
}catch let error{
print(error)
}
of course, You can call this if you don't need to distinguish between each error.
do{
try tom.marry(with: jack)
}catch let error{
print(error)
}
- Using try?, for the throws function with a return value, use try? to convert the result to an optional Value.
let tom = Person(sex: .male, money: 100000)
let jack = Person(sex: .male, money: 100000)
if let result = try? tom.marry(with: jack){//成功
}else{
print("Error happen")
}
Defer keywords
The defer keyword is used to handle @finally similar to the @[email protected] @finally in Ojective C.
For example, we open the file and if we throw an error, we always want to close the file Handle.
func contents(of filePath:String) throws -> String{
let file = open(filePath,O_RDWR)
defer {
close(file)
}
//...
}
The contents of the defer code block are executed before exiting the scope
About defer, There are two points to note
- Multiple defer are executed in reverse Order.
- Defer code blocks do not execute when your program encounters a serious error, such as fatalerror, or forces parsing nil, or Segfaults.
rethrow
Rethrow keywords are more common in Higher-order functions, so-called Higher-order functions, which are the parameters of a function or the return value is a function type.
The most common example is theSequenceprotocolmapmethod.
letarray = [1,2,3]letarray.map{$02}
Because the map function is passing in a closure, the closure may throw an Error. Errors thrown by parameters are passed up to the map function upwards.
Like what:
enum MapError : Error{
case invalid
}
func customMapper(input:Int) throws -> Int{
if input < 10{
throw MapError.invalid
}
return input + 1
}
let array = [1,2,3]
let result = array.map(customMapper)
This is a compilation that does not pass.
Call when required: follow the throws keyword mentioned above to
do {
let result = try array.map(customMapper)
} catch let error{
}
so, That's The essence of the rethrows Keyword.
The Rethrows keyword means that the function itself is throws when the parameter closure is marked as Throws. If the parameter closure does not throw an error, the function does Not.
With this keyword, you don't have to try-catch every Time.
Result type
We know that a function executes either successfully or Fails. When we succeed we want to return the data, we want to get the error message when we fail, this is the result type, a typical result type is as Follows:
enum Result<T>{
case success(T)
case failure(error:Error)
}
With the result type, you no longer need an optional value or Do-try-catch to wrap your Code.
We rewrite the above marry function with the result type:
extension Person{
func marry(with another: Person) -> Result<Bool>{
guard self.sex != another.sex else{
return .failure(error: MarryError.wrongSex)
}
guard self.money + another.money > 100000 else {
return .failure(error: MarryError.lackMoney)
}
return .success(true)
}
}
then, so call
let tom = Person(sex: .male, money: 100000)
let jack = Person(sex: .male, money: 100000)
let result = tom.marry(with: jack)
switch result {
case let .success(value):
print(value)
case let .failure(error):
print(error)
}
result Chain
There is an optional chain in Swift that handles successive invocations of multiple optional values. similarly, We can add chained calls to the result type:
- If the previous call result is. success, continue calling the next
- If the previous call result is. failure, The failure is passed to the next
We can use extension to achieve
extension Result{
func flatMap<V>(transform:(T) throws -> (V)) rethrows -> Result<V>{
switch self {
case let .failure(error):
return .failure(error: error)
case let .success(data):
return .success(try transform(data))
}
}
}
So you can call it that way.
resut.flatMap({//conversion 1}).flatMap(//conversion 2)...
Once failed, there is a flatmap conversion failure in the middle, then the conversion logic will not be executed
Advanced: The result type is large in the swift version of promise, see Promisekit's Source code, promise make asynchronous processing Elegant.
assert/precondition
At the beginning of this article, we dropped to objective C's assertion, and Swift also asserted Support. In swfit, an assertion is a function:
func assert(_ condition: @autoclosure () -> Bool,
_ message: @autoclosure () -> String = default,
file: StaticString = #file,
line: UInt = #line)
Assertions are only checked in debug mode to help developers find problems in the Code.
If you need to also check in relase mode, use theprecondition
func precondition(_ condition: @autoclosure () -> Bool,
_ message: @autoclosure () -> String = default,
file: StaticString = #file,
line: UInt = #line)
Bridge to Objective C
For the following objective methods that use Nserror to handle errors
//NSFileManager- (BOOL)removeItemAtURL:(NSURL *)URL error:(NSError * _Nullable *)error;
In swift, it is automatically converted into
func removeItem(atURLURL) throws
however, There are some problems with pure Swfit error bridging the objective C. Because Nserror needdomainandcodeso on detailed Information.
We can let Swift's error implement the CUSTOMNSERROR protocol to provide these required information.
enum MarryError : Error{
case lackMoney
case wrongSex
}
extension MarryError : CustomNSError{
static let errorDomain = "com.person.marryError"
var erroCode:Int{
switch self {
case .lackMoney:
return -100001
case .wrongSex:
return -100002
}
}
var errorUserInfo:[String:Any]{
return [:]
}
}
related, There are also two protocolsLocalizedErrorandRecoverableError
Error handling in Swift