Error
In development, the most easily overlooked content is the handling of errors. Experienced developers can be responsible for every line of code they write, and are very clear about when the code they write will have exceptions, so that they can handle errors in advance.
definition
Error in Swift is a protocol
public protocol Error : Sendable { }
Sendable: It is safe to pass sendable values from one concurrent domain to another - for example, you can pass sendable values as parameters when calling participants' methods.
Overview
Any type that declares conformance to the Error protocol can be used to represent an error in Swift’s error handling system. Because the Error protocol has no requirements of its own, you can declare conformance on any custom type you create.
In Swift's error handling system, any type that declares Error protocol compliant can be used to indicate an error. Because the Error protocol does not have its own requirements, you can declare consistency for any custom types you create.
Use enumeration to represent simple errors
Swift's enumeration is great for representing simple errors. Create a CompliantError
Enumeration of the protocol and provide a case for each possible error. If additional details about the error are required, you can use the associated value to include the information.
/// Declare an Int parsed Errorenum IntParsingError: Error { /// More than length case overflow /// Unparsed characters case invalidInput(String) /// Other error types case other }
extension Int { init(validating input: String) throws { for item in input { guard let _ = () else { throw (String(item)) } } if let int = () { self = int } else { throw } } } extension Character { func toInt() -> Int? { let str = String(self) if let int = Int(str) { return int } return nil } } extension String { public func toInt() -> Int? { if let num = NumberFormatter().number(from: self) { return } else { return nil } } }
let money: String = "100 yuan" let a = try? Int(validating: money)
At this time a is an Int? Type, if initialization fails, nil will be returned.
do { let price = try Int(validating: money) print(price) } catch (let invalid) { print("Invalid character: '(invalid)'") } catch { print("Overflow error") } catch { print("Other error") }
At this time, price is an Int type, and an exception will be thrown if the conversion fails.
Use structures or other types to represent complex errors
The following example uses structures to represent errors during parsing, including the file, method and line number where the error occurred:
struct ParseError: Error { enum errorKind { case pathError case InvalidFormat case other } let file: String let method: String let line: Int let type: errorKind } func parse(_ source: String) throws -> Int { throw ( file: "Users/Mccc/Log/", method: "LogMethod", line: 12, type: .InvalidFormat) }
Use pattern matching to conditionally catch errors. Here is how to catch any errors thrown by a function:
do { let info = try parse("123") print(info) } catch let e as ParseError { print("Parsing error: () [() : () : ()]") } catch { print("Other error: (error)") }
Three ways to deal with Error
- By try? Ignore Error
- pass
do - catch
CaptureError
- Not captured
Error
, add in the current functionthrows
statement,Error
It will be automatically thrown to the upper function. If the top-most function (main function) is still not capturedError
, then the program will terminate
Some related keywords
rethrows & throws
throws
Keywords are first used in function declarations and placed before the return type, such as the function signature of map in the standard library.
@frozen public struct Array<Element> { @inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T] }
Then inside the function, if a possible exception occurs, an exception can be thrown.
enum NegativeError: Error { /// Negative number case negative } let nums = [1, 2, 3, 4, -5] do { let strNums = try { (num) throws -> String in if num >= 0 { return String(num) } else { throw } } print(strNums) // Will no print } catch let err { print(err) }
- throws:: throw an exception in a function or method, so that the caller must explicitly handle possible exceptions.
- rethrows:: It does not throw or handle exceptions, it only plays the role of passing exceptions.
func methodThrows(num: Int) throws { if num < 0 { print("Throwing!") throw error } print("Executed!") } func methodRethrows(num: Int, f: (Int) throws -> ()) rethrows { try f(num) } do { try methodRethrows(num: 1, f: methodThrows) } catch _ { }
If you understand it simply, you canrethrows
See asthrows
"subclass" ofrethrows
The method can be used to overload those markedthrows
Methods or parameters, or used to satisfy thethrows
but the other way around is not.
try / try!/ try? / defer
- try: Similar to optional types, the compiler forces us to use the try keyword when using a room that may run out of the wrong one. Need and
do {} cathc {}
Use in combination. - Try! : Similar to the forced unpacking in optional types, the error will not be processed, but once the method throws an error, the program will crash
- Try? : It is somewhat similar to the optional chain in the optional type. If the method is correct, it will be executed in full; if an error occurs, the method ends early, but no error is thrown for processing.
- defer: Put the logic that must be executed in defer{}, which can ensure that the code in defer{} will be executed no matter which exit the method ends, and usually the defer{} is placed at the top of the method body.Defer code snippets are always executed at the end of the method life cycle。
fatalError
Unconditionally print the given message and stop execution.
fatalError("something wrong")
The existence meaning of fatalError is:
We can use assertions to rule out problems like this when debugging, but assertions will only work in the Debug environment, but in theAll assertions during Release compile will be disabled. When we encounter an input error, we cannot continue running, we generally consider terminating the program by generating a fatal error.
- Some methods in the parent class do not want others to call them, so you can add fatalError to the method. If you want to use the subclass, you must rewrite it.
- For all other methods that we do not want others to call at will but have to implement, we should use fatalError to avoid any possible misunderstandings.
Error-related agreements
Declare the structure of a car:
struct Car { }
Defines the error in which a car cannot drive.
extension Car { enum TroubleError: Error { /// Paralysis: The vehicle cannot drive case paralysis /// Insufficient oil case lackOilWarning /// Overload: You can continue driving after reducing staff. case overcrowding(Int) } }
LocalizedError
Describes an error that provides a localized message describing the cause of the error and provides more information about the error.
public protocol LocalizedError : Error { /// Provide localized message describing the error var errorDescription: String? { get } /// The reason for the error var failureReason: String? { get } /// Tips on how to recover var recoverySuggestion: String? { get } /// Other help text var helpAnchor: String? { get } }
extension : LocalizedError { /// Provide localized message describing the error var errorDescription: String? { switch self { case .paralysis: return NSLocalizedString("The car is paralyzed and cannot drive", comment: "Call tow truck repair") case .lackOilWarning: return NSLocalizedString("Insufficient oil", comment: "Come on Qing Dynasty") case .overcrowding(let count): return NSLocalizedString("Passenger overload,Overloaded number:(count)", comment: "Some passengers got off the bus after being overcrowded") } } /// The reason for the error var failureReason: String? { switch self { case .paralysis: return "The car is paralyzed and cannot drive" case .lackOilWarning: return "Insufficient oil" case .overcrowding(let count): return "Passenger overload,Overloaded number:(count)" } } /// Tips on how to recover var recoverySuggestion: String? { switch self { case .paralysis: return "Looking for emergency car repairs" case .lackOilWarning: return "Go to the gas station to refuel" case .overcrowding(let count): return "Number of overloaded people(count)People get out of the car" } } /// Other help text var helpAnchor: String? { switch self { case .paralysis: return "Emergency car repair phone: 0632-2347232" case .lackOilWarning: return "Map search for gas stations" case .overcrowding(_): return "Overloading is prohibited" } } }
CustomNSError
Error ⇋ NSError
// Initialize an NSErrorlet errorOC = (domain: "", code: 1000, userInfo: nil) // Convert to Errorlet swiftError = errorOC as Error print(swiftError) print() // Convert to NSErrorlet error = swiftError as NSError
I have always believed that NSError ⇋ Error ⇋ NSError can be converted without barriers. Since receiving this crash:
0 __swift_stdlib_bridgeErrorToNSError + 40 1 projectName loadDataDidFailed (file name.swift:69) ... ...
No specific reason was found in various channels. It is just recommended to use CustomNSError to handle it. If you have any students who know the specific reason, you can comment and reply.
Describes the error type that specifically provides a dictionary of domains, codes, and user information.
public protocol CustomNSError : Error { /// The domain of the error. static var errorDomain: String { get } /// The error code within the given domain. var errorCode: Int { get } /// The user-info dictionary. var errorUserInfo: [String : Any] { get } }
extension : CustomNSError { static var errorDomain: String { return "Domain" } var errorCode: Int { switch self { case .paralysis: return 1000 case .lackOilWarning: return 1001 case .overcrowding(_): return 1002 } } var errorUserInfo: [String : Any] { return [:] } }
Convert to NSError
extension { func toNSError() -> NSError { (domain: , code: errorCode, userInfo: errorUserInfo) } }
RecoverableError
Errors that can be recovered by providing users with several potential recovery options. This is mainly used in macOS applications using AppKit.
extension : RecoverableError { /// Error performing recovery when requested by the user. This is mainly used in macOS applications using AppKit. func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool { if recoveryOptionIndex == 0 { // Call for emergency vehicle rescue return false } else if recoveryOptionIndex == 1 { // Go to the gas station to refuel return true } else if recoveryOptionIndex == 2 { // Handle overload situations return true } fatalError("something wrong") } /// Provide a set of possible recovery options provided to the user var recoveryOptions: [String] { return ["Call for emergency vehicle rescue", "Go to the gas station to refuel", "Treat overload situations"] } }
KingfisherError
Kingfisher's error encapsulation is classic and is a typical case of using enums in swift. After reading this article, you will definitely have a deeper understanding of the use of swift enums and Errors, and at the same time add some advanced usage techniques for enums.
Original English meaning:
Represents all the errors which can happen in Kingfisher framework. Kingfisher related methods always throw a
KingfisherError
or invoke the callback withKingfisherError
as its error type. To handle errors from Kingfisher, you switch over the error to get a reason catalog, then switch over the reason to know error detail.
Chinese translation:
KingfisherErrorIndicates all errors that may occur in the Kingfisher framework. andKingfisherThe related method always throws aKingfisherErrorOrKingfisherErrorCallbacks as error type. To deal withKingfisherThe error requires a switch error to get the cause directory, and then switch the reason to understand the error details.
public enum KingfisherError: Error { // Indicates the reason for the error in the network request phase case requestError(reason: RequestErrorReason) // Indicates the cause of the error in the network response phase case responseError(reason: ResponseErrorReason) // Indicates an error in Kingfisher cache system case cacheError(reason: CacheErrorReason) // Indicates the cause of error in the image processing stage case processorError(reason: ProcessorErrorReason) // Indicates the reason for an error when setting an image in a view-related class case imageSettingError(reason: ImageSettingErrorReason) }
Five enumerations of associative value design RequestErrorReason, ResponseErrorReason, CacheErrorReason, ProcessorErrorReason, ImageSettingErrorReason
They are defined inKingfisherErrorIndependent enumerations, they have an inclusion and are included. It is important to understand this because with this inclusion management, it is necessary to pass theThis way is done.
Then the most important question is, how to connect the above five independent enums? The clever place of Kingfisher is here, with 5 independent enumerations representing the 5 major error types. In other words, this framework must have these 5 major error modules. We only need to design 5 suboptions for KingfisherError, and each suboption is associated with these 5 independent enumeration values.
This design is really clever. Just imagine, if you put all the errors in KingfisherError, it will appear very redundant. Everyone should experience the wonderful use of this design under Swift.
RequestErrorReason
Original English meaning:
Represents the error reason during networking request phase.
emptyRequest: The request is empty. Code 1001.
invalidURL: The URL of request is invalid. Code 1002.
taskCancelled: The downloading task is cancelled by user. Code 1003.
Chinese translation:
Indicates the cause of the error in the network request phase.
emptyRequest: The request is empty. Code 1001
invalidURL: The requested URL is invalid. Code 1002
taskCancelled: The download task was canceled by the user. Code 1003
public enum KingfisherError: Error { public enum RequestErrorReason { case emptyRequest case invalidURL(request: URLRequest) case taskCancelled(task: SessionDataTask, token: ) } }
passRequestErrorReason
We can clearly see that this is the reason for the request error. Pay attentionreason
This word, with or without this word, expresses completely different artistic conceptions, so Kingfisher's power is reflected in these details.
RequestErrorReasonIt is an enumeration, and it is included inKingfisherErrormiddle,This means that there can be another enum in the enum. So how do we use this situation? Look at the code below:
let reason =
The access to the enumeration is carried out at one level. Let's look at this line of code again:case invalidURL(request: URLRequest)
Not a function, it is a normal suboption of enumeration.(request: URLRequest)
It is an associated value of it. Compared with any suboption, we can associate any value. Its meaning is to bind these values to the suboptions for easy calling when needed.
ResponseErrorReason
Original English meaning:
Represents the error reason during networking response phase.
invalidURLResponse: The response is not a valid URL response. Code 2001.
invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002.
URLSessionError: An error happens in the system URL session. Code 2003.
dataModifyingFailed: Data modifying fails on returning a valid data. Code 2004.
noURLResponse: The task is done but no URL response found. Code 2005.
Chinese translation:
Indicates the cause of the error in the network response phase.
invalidURLResponse: This response is not a valid URL response. Code 2001.
invalidHTTPStatusCode: The response contains an invalid HTTP status code. Code 2002.
URLSessionError: An error occurred in the unified URL session. Code 2003.
dataModifyingFailed: Data modification failed when returning valid data. Code 2004.
noURLResponse: The task completed but no URL response was found. Code 2005.
public enum KingfisherError: Error { public enum ResponseErrorReason { case invalidURLResponse(response: URLResponse) case invalidHTTPStatusCode(response: HTTPURLResponse) case URLSessionError(error: Error) case dataModifyingFailed(task: SessionDataTask) case noURLResponse(task: SessionDataTask) } }
CacheErrorReason
Original English meaning:
Represents the error reason during Kingfisher caching system.
fileEnumeratorCreationFailed: Cannot create a file enumerator for a certain disk URL. Code 3001.
invalidFileEnumeratorContent: Cannot get correct file contents from a file enumerator. Code 3002.
invalidURLResource: The file at target URL exists, but its URL resource is unavailable. Code 3003.
cannotLoadDataFromDisk: The file at target URL exists, but the data cannot be loaded from it. Code 3004.
cannotCreateDirectory: Cannot create a folder at a given path. Code 3005.
imageNotExisting: The requested image does not exist in cache. Code 3006.
cannotConvertToData: Cannot convert an object to data for storing. Code 3007.
cannotSerializeImage: Cannot serialize an image to data for storing. Code 3008.
cannotCreateCacheFile: Cannot create the cache file at a certain fileURL under a key. Code 3009.
cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010.
Chinese translation:
Errors occurring in Kingfisher cache system.
fileEnumeratorCreationFailed: Unable to create a file enumerator for a disk URL. Code 3001
invalidFileEnumeratorContent: Cannot get the correct file content from the file enumerator. Code 3002
invalidURLResource: The file on the target URL exists, but its URL resource is not available. Code 3003
cannotLoadDataFromDisk: The file on the target URL exists, but the data cannot be loaded from it. Code 3004
cannotCreateDirectory: Unable to create a folder on a given path. Code 3005
imageNotExisting: The requested image does not exist in the cache. Code 3006
cannotConvertToData: cannot convert an object to data for storage. Code 3007
cannotSerializeImage: The image cannot be serialized to the data to be stored. Code 3008
cannotCreateCacheFile: Cannot create cache files on a file under a key. Code 3009
cannotSetCacheFileAttribute: Cannot set file attributes to a cached file. Code 3010
public enum KingfisherError: Error { public enum CacheErrorReason { case fileEnumeratorCreationFailed(url: URL) case invalidFileEnumeratorContent(url: URL) case invalidURLResource(error: Error, key: String, url: URL) case cannotLoadDataFromDisk(url: URL, error: Error) case cannotCreateDirectory(path: String, error: Error) case imageNotExisting(key: String) case cannotConvertToData(object: Any, error: Error) case cannotSerializeImage(image: KFCrossPlatformImage?, original: Data?, serializer: CacheSerializer) case cannotCreateCacheFile(fileURL: URL, key: String, data: Data, error: Error) case cannotSetCacheFileAttribute(filePath: String, attributes: [FileAttributeKey : Any], error: Error) } }
ProcessorErrorReason
Original English meaning:
Represents the error reason during image processing phase.
processingFailed: Image processing fails. There is no valid output image from the processor. Code 4001.
Chinese translation:
Represents the cause of errors during the image processing phase.
processingFailed: Image processing failed. The processor does not have a valid output image. Code 4001
public enum KingfisherError: Error { public enum ProcessorErrorReason { case processingFailed(processor: ImageProcessor, item: ImageProcessItem) } }
ImageSettingErrorReason
Original English meaning:
Represents the error reason during image setting in a view related class.
emptySource: The input resource is empty or nil. Code 5001.
notCurrentSourceTask: The source task is finished, but it is not the one expected now. Code 5002.
dataProviderError: An error happens during getting data from an ImageDataProvider. Code 5003.
alternativeSourcesExhausted: No more alternative Source can be used in current loading process. Code 5004
Chinese translation:
Indicates the reason for an error when setting an image in a view-related class.
emptySource: The input resource is empty or "nil". Code 5001
notCurrentSourceTask: The source task has been completed, but is not the task you are expecting now. Code 5002
dataProviderError: An error occurred while fetching data from ImageDataProvider. Code 5003
alternativeSourcesExhausted: No more alternative "sources" can be used during the current loading process. It means. alternativeSources are used,KingfisherAttempting to recover from the initial error, but using all given alternative sources still fails. The associated value contains all errors encountered during the loading process, including the original source loading error and all alternative source errors.
public enum KingfisherError: Error { public enum ImageSettingErrorReason { case emptySource case notCurrentSourceTask(result: RetrieveImageResult?, error: Error?, source: Source) case dataProviderError(provider: ImageDataProvider, error: Error) case alternativeSourcesExhausted([PropagationError]) } }
Convenient inspection methods & attributes
Whether the task was cancelled
public var isTaskCancelled: Bool { if case .requestError(reason: .taskCancelled) = self { return true } return false }
Is the return status code invalid?
yesThis case, and the associated code is consistent with the given one.
public func isInvalidResponseStatusCode(_ code: Int) -> Bool { if case .responseError(reason: .invalidHTTPStatusCode(let response)) = self { return == code } return false }
Is the response status code invalid?
public var isInvalidResponseStatusCode: Bool { if case .responseError(reason: .invalidHTTPStatusCode) = self { return true } return false }
Is the current task
Check if it isType error. When the old image settings task is still running and the new image settings task starts, the new task identifier is set and the old task is overwritten. When the old task is over, a
.notCurrentSourceTask
The error will be thrown, letting you know that the setup process ends with a certain result, butimage view or button
Not set.
public var isNotCurrentTask: Bool { if case .imageSettingError(reason: .notCurrentSourceTask(_, _, _)) = self { return true } return false }
localized message describing
In development, if a program encounters errors, we often show users more intuitive information, which requires us to convert the error information into easy-to-understand content. Therefore, we just need to implement the LocalizedError protocol.
extension KingfisherError: LocalizedError { public var errorDescription: String? { switch self { case .requestError(let reason): return case .responseError(let reason): return case .cacheError(let reason): return case .processorError(let reason): return case .imageSettingError(let reason): return } } } extension KingfisherError: CustomNSError { public var errorCode: Int { switch self { case .requestError(let reason): return case .responseError(let reason): return case .cacheError(let reason): return case .processorError(let reason): return case .imageSettingError(let reason): return } } }
Add descriptions to the five major error enumerations by extending.
extension { var errorDescription: String? { switch self { case .emptyRequest: return "The request is empty or `nil`." case .invalidURL(let request): return "The request contains an invalid or empty URL. Request: (request)." case .taskCancelled(let task, let token): return "The session task was cancelled. Task: (task), cancel token: (token)." } } var errorCode: Int { switch self { case .emptyRequest: return 1001 case .invalidURL: return 1002 case .taskCancelled: return 1003 } } } extension { var errorDescription: String? { switch self { case .invalidURLResponse(let response): return "The URL response is invalid: (response)" case .invalidHTTPStatusCode(let response): return "The HTTP status code in response is invalid. Code: (), response: (response)." case .URLSessionError(let error): return "A URL session error happened. The underlying error: (error)" case .dataModifyingFailed(let task): return "The data modifying delegate returned `nil` for the downloaded data. Task: (task)." case .noURLResponse(let task): return "No URL response received. Task: (task)," } } var errorCode: Int { switch self { case .invalidURLResponse: return 2001 case .invalidHTTPStatusCode: return 2002 case .URLSessionError: return 2003 case .dataModifyingFailed: return 2004 case .noURLResponse: return 2005 } } } extension { var errorDescription: String? { switch self { case .fileEnumeratorCreationFailed(let url): return "Cannot create file enumerator for URL: (url)." case .invalidFileEnumeratorContent(let url): return "Cannot get contents from the file enumerator at URL: (url)." case .invalidURLResource(let error, let key, let url): return "Cannot get URL resource values or data for the given URL: (url). " + "Cache key: (key). Underlying error: (error)" case .cannotLoadDataFromDisk(let url, let error): return "Cannot load data from disk at URL: (url). Underlying error: (error)" case .cannotCreateDirectory(let path, let error): return "Cannot create directory at given path: Path: (path). Underlying error: (error)" case .imageNotExisting(let key): return "The image is not in cache, but you requires it should only be " + "from cache by enabling the `.onlyFromCache` option. Key: (key)." case .cannotConvertToData(let object, let error): return "Cannot convert the input object to a `Data` object when storing it to disk cache. " + "Object: (object). Underlying error: (error)" case .cannotSerializeImage(let image, let originalData, let serializer): return "Cannot serialize an image due to the cache serializer returning `nil`. " + "Image: (String(describing:image)), original data: (String(describing: originalData)), " + "serializer: (serializer)." case .cannotCreateCacheFile(let fileURL, let key, let data, let error): return "Cannot create cache file at url: (fileURL), key: (key), data length: (). " + "Underlying foundation error: (error)." case .cannotSetCacheFileAttribute(let filePath, let attributes, let error): return "Cannot set file attribute for the cache file at path: (filePath), attributes: (attributes)." + "Underlying foundation error: (error)." } } var errorCode: Int { switch self { case .fileEnumeratorCreationFailed: return 3001 case .invalidFileEnumeratorContent: return 3002 case .invalidURLResource: return 3003 case .cannotLoadDataFromDisk: return 3004 case .cannotCreateDirectory: return 3005 case .imageNotExisting: return 3006 case .cannotConvertToData: return 3007 case .cannotSerializeImage: return 3008 case .cannotCreateCacheFile: return 3009 case .cannotSetCacheFileAttribute: return 3010 } } } extension { var errorDescription: String? { switch self { case .processingFailed(let processor, let item): return "Processing image failed. Processor: (processor). Processing item: (item)." } } var errorCode: Int { switch self { case .processingFailed: return 4001 } } } extension { var errorDescription: String? { switch self { case .emptySource: return "The input resource is empty." case .notCurrentSourceTask(let result, let error, let resource): if let result = result { return "Retrieving resource succeeded, but this source is " + "not the one currently expected. Result: (result). Resource: (resource)." } else if let error = error { return "Retrieving resource failed, and this resource is " + "not the one currently expected. Error: (error). Resource: (resource)." } else { return nil } case .dataProviderError(let provider, let error): return "Image data provider fails to provide data. Provider: (provider), error: (error)" case .alternativeSourcesExhausted(let errors): return "Image setting from alternaive sources failed: (errors)" } } var errorCode: Int { switch self { case .emptySource: return 5001 case .notCurrentSourceTask: return 5002 case .dataProviderError: return 5003 case .alternativeSourcesExhausted: return 5004 } } }
The above is a detailed explanation of the basic example of Swift Error reconstruction. For more information about the basic information of Swift Error reconstruction, please pay attention to my other related articles!