1. Literal words

Before we get into patterns, let’s look at what literals are.

var age = 10
var isShow = false
var name = "Jack"
Copy the code

10, false, “Jack” in the code above is literal.

1.1. Literal types

Default types for common literals:

public typealias IntegerLiteralType = Int
public typealias FloatLiteralType = Double
public typealias BooleanLiteralType = Bool
public typealias StringLiteralType = String
Copy the code

The default type of a literal can be changed via typeAlias (it is generally not necessary) :

public typealias FloatLiteralType = Float
public typealias IntegerLiteralType = UInt8
var age = 10 / / UInt8 type
var height = 20.0 / / Float type
Copy the code

Most of Swift’s native types support direct initialization via literals (without calling the initializer directly) :

Bool,Int,Float,Double,String,Array,Dictionary,Set,Optional etc.
Copy the code

1.2. Literal protocols

Swift native types can be initialized via literals because they comply with the corresponding protocol.

Bool : ExpressibleByBooleanLiteral
Int : ExpressibleByIntegerLiteral
Float,Double : ExpressibleByIntegerLiteral,ExpressibleByFloatLiteral
Dictionary : ExpressibleByDictionaryLiteral
String : ExpressibleByStringLiteral
Array,Set : ExpressibleByArrayLiteral
Optinal : ExpressibleByNilLiteral
Copy the code

Sample code:

var b: Bool = false // ExpressibleByBooleanLiteral
var i: Int = 10 // ExpressibleByIntegerLiteral
var f0: Float = 10 // ExpressibleByIntegerLiteral
var f1: Float = 10.0 // ExpressibleByFloatLiteral
var d0: Double = 10 // ExpressibleByIntegerLiteral
var d1: Double = 10.0 // ExpressibleByFloatLiteral
var s: String = "idbeny" // ExpressibleByStringLiteral
var arr: Array = [1.2.3] // ExpressibleByArrayLiteral
var set: Set = [1.2.3] // ExpressibleByArrayLiteral
var dict: Dictionary = ["name" : "daben"] // ExpressibleByDictionaryLiteral
var o: Optional<Int> = nil // ExpressibleByNilLiteral
Copy the code

1.3. Literal protocol application

Example code 1:

BoolType directly assigned toIntType of a variable that directly returns an error:

To avoid an error, simply add a protocol-compliant extension to Int:

extension Int : ExpressibleByBooleanLiteral {
    public init(booleanLiteral value: Bool) {
        self = value ? 1 : 0}}var num: Int = true
print(num) // Output: 1
Copy the code

Example code 2:

class Student: ExpressibleByIntegerLiteral.ExpressibleByFloatLiteral.ExpressibleByStringLiteral.CustomStringConvertible {
    var name: String = ""
    var score: Double = 0
    required init(floatLiteral value: Double) {
        self.score = value
    }
    required init(integerLiteral value: Int) {
        self.score = Double(value)
    }
    required init(stringLiteral value: String) {
        self.name = value
    }
    // Support Unicode and special characters
    required init(unicodeScalarLiteral value: String) {
        self.name = value
    }
    required init(extendedGraphemeClusterLiteral value: String) {
        self.name = value
    }
    var description: String {
        "name=\(name), score=\(score)"}}var stu: Student = 90
print(stu) // Output: name=, score=90.0

stu = 98.5
print(stu) Name =, score=98.5

stu = "idbeny"
print(stu) Name =idbeny, score=0.0
Copy the code

Example code 3:

struct Point {
    var x = 0.0, y = 0.0
}
extension Point : ExpressibleByArrayLiteral.ExpressibleByDictionaryLiteral {
    init(arrayLiteral elements: Double...). {
        guard elements.count > 0 else {
            return
        }
        self.x = elements[0]
        guard elements.count > 1 else {
            return
        }
        self.y = elements[1]}init(dictionaryLiteral elements: (String.Double).) {
        for (k, v) in elements {
            if k = = "x" {
                self.x = v
            } else if k = = "y" {
                self.y = v
            }
        }
    }
}
var p: Point = [5.12.10.24]
print(p) // Point(x: 5.12, y: 10.24)

p = ["x" : 11."y" : 22]
print(p) // Point(x: 11.0, y: 22.0)
Copy the code

Ii. Pattern

Patterns are rules for matching, such as case of switch, catch of error, condition of if/guard/while/for statement, etc.

The modes in Swift are:

  • Wildcard Pattern
  • Identifier Pattern
  • Value Binding Pattern
  • Tuple Pattern
  • Enumeration Pattern
  • Optional Pattern
  • Type-casting Pattern
  • Expression Pattern

2.1. Wildcard mode

  • _Match any value
  • _?Match thenilvalue

Sample code:

enum Life {
    case human(name: String, age: Int?).case animal(name: String, age: Int?). }func check(_ life: Life) {
    switch life {
    case .human(let name, _) :print("human", name)
    case .animal(let name, _?) :print("animal", name)
    default:
        print("other")
    }
}
check(.human(name: "Rose", age: 20)) // Output: human Rose
check(.human(name: "Jack", age: nil)) // Output: human Jack
check(.animal(name: "Dog", age: 5)) // Output: animal Dog
check(.animal(name: "Cat", age: nil)) // Output: other
Copy the code

The _ in case. Human (let name, _): matches any value.

case .animal(let name, _?) : the _? That’s matching non-nil values.

2.2. Identifier pattern

Assign values to corresponding variable and constant names.

var age = 10
let name = "idbeny"
Copy the code

2.3. Value binding mode

Bind the value of the corresponding position to a variable/constant.

let point = (3.2)
switch point {
case let (x, y):
    print("x:\(x), y:\(y)")}// Output: x:3, y:2
Copy the code

2.4. Tuple mode

Value binding and wildcard patterns are also essential.

Example code 1 (array) :

let points = [(0.0), (1.0), (2.0)]
for (x, _) in points {
    print(x)
}
/*
 输出 :
 0
 1
 2
 */
Copy the code

Example code 2 (case) :

let name: String? = "idbeny"
let age = 18
let info: Any = [1.2]
switch (name, age, info) {
case (_?._._ as String) :print("case")
default:
    print("default")}// Output: default
Copy the code

Example code three (dictionary) :

var scores = ["jack" : 98."rose" : 100."kate" : 86]
for (name, score) in scores {
    print(name, score)
}
/* Output: jack 98 Kate 86 Rose 100 */
Copy the code

2.5. Enumerate Case schema

The if case statement is equivalent to the switch statement with only 1 case.

Example code 1:

let age = 2
func test(a) {
    // The original way of writing
    if age > = 0 && age < = 9 {
        print("[0, 9].")}// Enumerate case mode
    if case 0.9  = age {
        print("[0, 9].")}guard case 0.9 = age else { return }
    print("[0, 9].")
}
test()
/* Output: [0, 9] [0, 9] */
Copy the code

if case 0… 9 = age matches the value of age with the condition behind the case.

The following code is exactly equivalent to the if case of the above example:

switch age {
case 0.9:
    print("[0, 9].")
default:
    break
}
// Output: [0, 9]
Copy the code

Example code 2:

let age = 2
let ages: [Int? ]= [2.3.nil.5]
for case nil in ages {
    print("Has nil value")
    break
}
// Output: nil value
Copy the code

Example code 3:

let points = [(1.0), (2.1), (3.0)]
for case let (x, 0) in points {
    print(x)
}
// Output: 1, 3

// Error:
// for (x, 0) in points {
// print(x)
// }
Copy the code

2.6. Optional modes

Example code 1:

let age: Int? = 42
if case .some(let x) = age {
    print(x)
}
// Output: 42

// x? Representative is not empty
if case let x? = age {
    print(x)
}
// Output: 42
Copy the code

Example code 2:

let ages: [Int? ]= [nil.2.3.nil.5]
for case let age? in ages {
    print(age)
}
/* Output: 2, 3, 5 */
Copy the code

The code for example 2 above is equivalent to the following:

for item in ages {
    // Optional binding
    if let age = item {
        print(age)
    }
}
/* Output: 2, 3, 5 */
Copy the code

Example code 3:

func check(_ num: Int?). {
    switch num {
    case 2?:
        print("2")
    case 4?:
        print("4")
    case 6?:
        print("6")
    case _?:
        print("other")
    case _:
        print("nil")
    }
}
check(4) // Output: 4
check(8) // Output: other
check(nil) // Output: nil
Copy the code

2.7. Type conversion mode

Mainly is and as.

Example code 1:

let num: Any = 6
switch num {
case is Int:
    print("is Int", num)
default:
    break
}
// output: is Int 6
Copy the code

Num = Any; case is Int; num = Any;

If you need to force and determine the type, you can use as:

switch num {
case let n as Int:
    print("as Int", n)
default:
    break
}
// As Int 6
Copy the code

N is an Int, but num is still an Any. If num is not Int, the current case will be skipped and the next case will be matched.

Example code 2:

class Animal {
    func eat(a) {
        print(type(of: self), "eat")}}class Dog : Animal {
    func run(a) {
        print(type(of: self), "run")}}class Cat : Animal {
    func jump(a) {
        print(type(of: self), "jump")}}func check(_ animal: Animal) {
    switch animal {
    case let dog as Dog:
        dog.eat()
        dog.run()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
/*
 输出:
 Dog eat
 Dog run
 */

check(Cat())
/* output: Cat eat */
Copy the code

In the example above, how can jump be executed if case is Cat is matched? Because animal is animal.

Animal can be cast:

(animal as? Cat)? .jump()Copy the code

2.8. Expression patterns

Expression patterns are used in cases:

let point = (1.2)
switch point {
case (0.0) :print("(0, 0) is at the origin.")
case (-2.2.-2.2) :print("(\(point.0).\(point.1)) is near the origin.")
default:
    print("The point is at (\(point.0).\(point.1)).")}// output :(1, 2) is near the origin.
Copy the code

View the above sample code through assembly, found that the sample program is used~ =Operator to match:

In fact, in Swift, some complex switch matches use the ~= operator, but not all switches use this operator.

You can customize the matching rules of expression patterns by overloading operators.

2.8.1. Custom expression patterns

Example code 1:

struct Student {
    var score = 0, name = ""
    static func ~ = (pattern: Int.value: Student) -> Bool {
        value.score > = pattern
    }
    static func ~ = (pattern: ClosedRange<Int>, value: Student) -> Bool {
        pattern.contains(value.score)
    }
    static func ~ = (pattern: Range<Int>, value: Student) -> Bool {
        pattern.contains(value.score)
    }
}
Copy the code

Use example code 1:

var stu = Student(score: 72, name: "idbeny")
switch stu {
case 100: print("> = 100")
case 90: print("> = 90")
case 80..<90: print("[80, 90)." ")
case 60.79: print("[60, 79]")
case 0: print("> = 0")
default: break
}
// Output: [60, 79]
Copy the code

stuWhat is andInt,RangWhat about matching? rewrite~ =Operator.

The return value must be Bool:

// Pattern: type after case
// value: type after switch
static func ~ = (pattern: Int.value: Student) -> Bool{}Copy the code

Example code 2 (if case is essentially switch) :

if case 60 = stu {
    print("> = 60")}// Output: >=60
Copy the code

Example code 3:

var info = (Student(score: 70, name: "daben"), "Pass")
switch info {
case let (60, text):
    print(text)
default:
    break
}
// Output: pass
Copy the code

Example code 4:

extension String {
    static func ~ = (pattern: (String) - >Bool.value: String) -> Bool {
        pattern(value)
    }
}

func hasPrefix(_ prefix: String)- > ((String) - >Bool) {{$0.hasPrefix(prefix)}}func hasSuffix(_ suffix: String)- > ((String) - >Bool) {{$0.hasSuffix(suffix) }
}

var str = "idbeny"
switch str {
case hasPrefix("i"), hasSuffix("y") :print("Begins with I or ends with Y")
default:
    break
}
// Output: starts with I or ends with y
Copy the code

Example code 5:

func isEven(_ i: Int) -> Bool {
    i % 2 = = 0
}
func isOdd(_ i: Int) -> Bool {
    i % 2 ! = 0
}

extension Int {
    static func ~ = (pattern: (Int) - >Bool.value: Int) -> Bool {
        pattern(value)
    }
}

var age = 10
switch age {
case isEven:
    print("Even")
case isOdd:
    print("Odd")
default:
    print("Other")}// Output: even
Copy the code

You can also define more custom operators:

prefix operator ~ >
prefix operator ~ > =
prefix operator ~ <
prefix operator ~ < =
prefix func ~ > (_ i: Int)- > ((Int) - >Bool) {{$0 > i } }
prefix func ~ > = (_ i: Int)- > ((Int) - >Bool) {{$0 > = i } }
prefix func ~ < (_ i: Int)- > ((Int) - >Bool) {{$0 < i } }
prefix func ~ < = (_ i: Int)- > ((Int) - >Bool) {{$0 < = i } }

switch age {
case ~ > =0.~ < =10:
    print("1")
case ~ >10.~ <20:
    print("2")
default:
    break
}
// Output: 1
Copy the code

The >, =, >=, <, <= operators are infix operators. In order not to affect the characteristics of the original operator, a new operator is created by adding a ~ symbol in front of the original operator.

2.8.2. where

You can use WHERE to add matching conditions to pattern matching.

Example code 1 (case) :

var data = (10."Jack")
switch data {
case let (age, _) where age > 10:
    print(data.1."age>10")
case let (age, _) where age > 0:
    print(data.1."age>0")
default:
    break
}
// Output: Jack age>0
Copy the code

Example code two (for) :

var ages = [10.20.12.55.30]
for age in ages where age > 20 {
    print(age)
}
// Output: 55, 30
Copy the code

Example code three (protocol) :

protocol Stackable {
    associatedtype Element
}
protocol Container {
    associatedtype Stack : Stackable where Stack.Element : Equatable
}
Copy the code

Example code 4 (func) :

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

Extension:

extension Container where Self.Stack.Element : Hashable {}Copy the code

For more articles in this series, please pay attention to wechat official account [1024 Planet].