Error handling

Wrong type

Common mistakes during development are

  • Syntax error (compilation error)
  • Logical error
  • Runtime error (may cause a flash back, also known as an exception)
  • .

Custom error

You can customize runtime Error messages in Swift using the Error protocol

enum SomeError: Error {
    case illegalArg(String)
    case outOffBounds(Int, Int)
    case outOfMemory
}
Copy the code

Throws a custom Error inside a function. Functions that may throw an Error must add a throws declaration

func divide(_ num1: Int, _ num2: Throws -> Int {if num2 == 0 {throw SomeError. IllegalArg ("0 cannot be used as a didier ")} return num1 / num2}Copy the code

You need to use a try to call a function that might throw an Error

var result = try divide(20, 10)
Copy the code

The condition in which an error message is thrown

Catch the do –

You can use do — catch to catch errors

do { try divide(20, 0) } catch let error { switch error { case let SomeError.illegalArg(msg): Print (" error ", MSG) default: print(" error ")}}Copy the code

After an Error is thrown, the next line of the try will stop running until the end of the scope

func test() { print("1") do { print("2") print(try divide(20, Print ("3")} catch let SomeError. IllegalArg (MSG) {print(" parameter error :", MSG)} catch let SomeError. OutOffBounds (size, index) {print(" subbounds :", "size=\(size)", "Index =\(index)")} catch SomeError. OutOfMemory {print(" memory overflow ")} catch {print(" other error ")} print("4")} test() //1 //2 // Parameter exception: 0 cannot be used as divisor //4Copy the code

The catch scope has error variables that can be caught by default

do {
    try divide(20, 0)
} catch {
    print(error)
}
Copy the code

Processing Error

There are two ways to handle Error

Catch an Error by do — catch

do {
    print(try divide(20, 0))
} catch is SomeError {
    print("SomeError")
}
Copy the code

If you do not capture an Error, add a throws declaration to the current function, and the Error is automatically thrown to the upper function

If the top-level function main still does not catch an Error, the program terminates

func test() throws {
    print("1")
    print(try divide(20, 0))
    print("2")
}

try test()

// 1
// Fatal error: Error raised at top level
Copy the code

Calls to functions written inside the function without capturing errors are reported, while calls to functions written outside the function are not

Then we add do catch, because the catch Error is not detailed enough, we need to catch all the Error information

At this point we add throws

func test() throws { print("1") do { print("2") print(try divide(20, 0)) print("3") } catch let error as SomeError { print(error) } print("4") } try test() // 1 // 2 // IllegalArg ("0 cannot be divisor ") // 4Copy the code

Or add a catch to catch all the other Error cases

func test() {
    print("1")
    do {
        print("2")
        print(try divide(20, 0))
        print("3")
    } catch let error as SomeError {
        print(error)
    } catch {
        print("其他错误情况")
    }

    print("4")
}

test()
Copy the code

Take a look at the following sample code. What is output after execution

func test0() throws { print("1") try test1() print("2") } func test1() throws { print("3") try test2() print("4") } func  test2() throws { print("5") try test3() print("6") } func test3() throws { print("7") try divide(20, 0) print("8") } try test0()Copy the code

The following is printed and an error message is thrown

try

You can use try? And the try! Call a function that might raise an Error, so you don’t have to deal with errors

func test() {
    print("1")
    
    var result1 = try? divide(20, 10) // Optional(2), Int?
    var result2 = try? divide(20, 0) // nil
    var result3 = try! divide(20, 10) // 2, Int
    
    print("2")
}

test()
Copy the code

A and B are equivalent

var a = try? divide(20, 0)
var b: Int?

do {
     b = try divide(20, 0)
} catch { b = nil }
Copy the code

rethrows

Rethrows indicates that the function itself does not throw an error, but calling the closure parameter does, and it will throw the error up

func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
    print(try fn(num1, num2))
}

// Fatal error: Error raised at top level
try exec(divide, 20, 0)
Copy the code

Null merge operators are declared using REthrows

defer

The defer statement, which defines the code that must be executed before leaving the block in any way (throwing errors, returns, and so on)

The defer statement will be deferred until the end of the current scope

func open(_ filename: String) -> Int { print("open") return 0 } func close(_ file: Int) { print("close") } func processFile(_ filename: Throws {let file = open(filename) defer {close(file)} // Use file //..... } try processFile("test.txt") // open // close // Fatal error: Error raised at top levelCopy the code

The defer statements are executed in reverse of the order defined

func fn1() { print("fn1") } func fn2() { print("fn2") } func test() { defer { fn1() } defer { fn2() } } test() // fn2 //  fn1Copy the code

It’s an assert.

Many programming languages have an assertion mechanism that throws runtime errors if specified conditions are not met. It is often used to determine conditions during debugging

By default, Swift assertions are only valid in Debug mode and ignored in Release mode

Added Swift Flags default behavior for modifying assertions

  • -assert-config Release: forces assertion to be closed
  • -assert-config Debug: Forcibly enables assertion

fatalError

If you run into a serious problem and want to end the program, you can throw an error directly using the fatalError function

This is an error that cannot be caught by do — catch

With fatalError, there is no need to write return

Func test(_ num: Int) -> Int {if num >= 0 {return 1} fatalError("num must not be less than 0")}Copy the code

For some methods that you have to implement but don’t want others to call, consider using fatalError internally

class Person { required init() {} }
class Student: Person {
    required init() {
        fatalError("don't call Student.init")
    }
    
    init(score: Int) {
        
    }
}

var stu1 = Student(score: 98)
var stu2 = Student()
Copy the code

Local scope

Local scopes can be implemented using DO

do {
    let dog1 = Dog()
    dog1.age = 10
    dog1.run()
}

do {
    let dog2 = Dog()
    dog2.age = 10
    dog2.run()
}
Copy the code

Generics

The basic concept

Generics parameterize types, increasing code reuse and reducing code volume

func swapValues<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}

var i1 = 10
var i2 = 20
swap(&i1, &i2)
print(i1, i2) // 20, 10

struct Date {
    var year = 0, month = 0, day = 0
}

var d1 = Date(year: 2011, month: 9, day: 10)
var d2 = Date(year: 2012, month: 10, day: 20)
swap(&d1, &d2)
print(d1, d2) // Date(year: 2012, month: 10, day: 20), Date(year: 2011, month: 9, day: 10)
Copy the code

Generic functions assign values to variables

func test<T1, T2>(_ t1: T1, _ t2: T2) {}
var fn: (Int, Double) -> () = test
Copy the code

The generic type

class Stack<E> {
    var elements = [E]()
    
    func push(_ element: E) {
        elements.append(element)
    }
    
    func pop() -> E {
        elements.removeLast()
    }
    
    func top() -> E {
        elements.last!
    }
    
    func size() -> Int {
        elements.count
    }
}

class SubStack<E>: Stack<E> {
    
}

var intStack = Stack<Int>()
var stringStack = Stack<String>()
var anyStack = Stack<Any>()
Copy the code
struct Stack<E> { var elements = [E]() mutating func push(_ element: E) { elements.append(element) } mutating func pop() -> E { elements.removeLast() } func top() -> E { elements.last! } func size() -> Int { elements.count } } var stack = Stack<Int>() stack.push(11) stack.push(22) stack.push(33) print(stack.top()) // 33 print(stack.pop()) // 33 print(stack.pop()) // 22 print(stack.pop()) // 11 print(stack.size()) / / 0Copy the code
enum Score<T> { case point(T) case grade(String) } let score0 = Score<Int>.point(100) let score1 = Score.point(99) let Score = Score. Point (99.5) let score3 = Score<Int>. Grade ("A")Copy the code

Associated Type

Associative types are used to define a placeholder name for the type used in the protocol

protocol Stackable {
    associatedtype Element
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}

struct Stack<E>: Stackable {
    var elements = [E]()
    
    mutating func push(_ element: E) {
        elements.append(element)
    }
    
    mutating func pop() -> E {
        elements.removeLast()
    }
    
    func top() -> E {
        elements.last!
    }
    
    func size() -> Int {
        elements.count
    }
}

class StringStack: Stackable {
    var elements = [String]()
    
    func push(_ element: String) {
        elements.append(element)
    }
    
    func pop() -> String {
        elements.removeLast()
    }
    
    func top() -> String {
        elements.last!
    }
    
    func size() -> Int {
        elements.count
    }
}

var ss = StringStack()
ss.push("Jack")
ss.push("Rose")
Copy the code

A protocol can have multiple association types

protocol Stackable {
    associatedtype Element
    associatedtype Element2
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}
Copy the code

Type constraints

protocol Runnable { }
class Person { }

func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}
Copy the code
protocol Stackable {
    associatedtype Element: Equatable
}

class Stack<E: Equatable>: Stackable {
    typealias Element = E
}

func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
        return false
}

var stack1 = Stack<Int>()
var stack2 = Stack<String>()
equal(stack1, stack2)
Copy the code

Note The protocol type

Take a look at the sample code below to analyze

protocol Runnable { }
class Person: Runnable { }
class Car: Runnable { }

func get(_ type: Int) -> Runnable {
    if type == 0 {
        return Person()
    }
    
    return Car()
}

var r1 = get(0)
var r2 = get(1)
Copy the code

If the protocol has AssociatedType

protocol Runnable { associatedtype Speed var speed: Speed { get } } class Person: Runnable { var speed: Double {0.0}} class Car: Runnable {var speed: Int {0}}Copy the code

This will give you an error, because you don’t know at compile time what the real type of Speed is, okay

This can be solved with generics

protocol Runnable { associatedtype Speed var speed: Speed { get } } class Person: Runnable { var speed: Double {0.0}} class Car: Runnable {var speed: Int {0}} func get<T: Runnable>(_ type: Int) -> T { if type == 0 { return Person() as! T } return Car() as! T } var r1: Person = get(0) var r2: Car = get(1)Copy the code

You can also declare an opaque type using the some keyword

The some constraint can only return one type

protocol Runnable { associatedtype Speed var speed: Speed { get } } class Person: Runnable { var speed: Double {0.0}} class Car: Runnable {var speed: Int {0}} func get(_ type) Int) -> some Runnable { return Car() } var r1 = get(0) var r2 = get(1)Copy the code

In addition to returning value types, some can also be used for attribute types

protocol Runnable {
    associatedtype Speed
}

class Dog: Runnable {
    typealias Speed = Double
}

class Person {
    var pet: some Runnable {
        return Dog()
    }
}
Copy the code

The nature of generics

We use the following example code to analyze its internal specific how to achieve

func swapValues<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}

var i1 = 10
var i2 = 20
swap(&i1, &i2)
print(i1, i2) // 20, 10

var d1 = 10.0
var d2 = 20.0
swap(&d1, &d2)
print(d1, d2) // 20.0, 10.0
Copy the code

After disassembly

From the point of view of calling both exchange methods, you end up calling the same function because the address of the function is the same; Int’s metadata and Double’s metadata are passed as parameters, so the metadata should specify the corresponding type to handle the metadata

The nature of alternatives

The nature of the optional nature is the enum type

We can look in the main file

The real way to write grammatical sugar is as follows

var age: Int? Var ageOpt0: Optional<Int> = Optional<Int>. Some (10) var ageOpt1: Optional = .some(10) var ageOpt2 = Optional.some(10) var ageOpt3 = Optional(10)Copy the code
var age: Int? Var ageOpt0: Optional<Int> =. None var ageOpt1 = Optional<Int>. NoneCopy the code
var age: Int? = .none
age = 10
age = .some(20)
age = nil
Copy the code

The use of optional options in switch

switch age { case let v? // Add? V print("some", v) case nil: print("none")} Switch age {case let.some (v): print("some", v) case. None: print("some", v) case. print("none") }Copy the code

Multiple options

var age_: Int? = 10
var age: Int?? = age_
age = nil

var age0 = Optional.some(Optional.some(10))
age0 = .none
var age1: Optional<Optional> = .some(.some(10))
age1 = .none
Copy the code
var age: Int?? = 10
var age0: Optional<Optional> = 10
Copy the code