Generics can be found in Java,C++ and many other languages. C# makes the most of generics, OC has generics (for example, arrays in OC, you can limit them to nsstrings), and Swift has a more diverse range of generics.

Generic functions

1.1. Generics can parameterize types, improve code reuse and reduce code load

Sample code:

var n1 = 10 var n2 = 20 func swapValues(_ a: inout Int, _ b: Inout Int) {(a, b) = (b, a)} swapValues (& n1, and n2) print (" a = \ (n1), b = \ "(n2)) / / output: a = 20, b = 10Copy the code

If swapValues in the sample code above were passed in a different type of parameter, it would not work. This is where generics are considered.

The function name is followed by

to indicate that the function takes a generic parameter, and that the parameter type is also modified by T (T is not a fixed notation, but can also be S, ABC, or any other identifier that only represents an indeterminate type).

Sample generic code:

func swapValues<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}
swapValues(&n1, &n2)
Copy the code

At this point, you can pass in any type of parameter, but the parameter type must be consistent.

callswapValuesI can’t write it after deltaswapValues<Int>(&n1, &n2)Because the argument already specifies what type to pass in.

When a generic function is declared, the generic identifier must not be omitted. Otherwise, it is an ordinary function and the compiler cannot recognize generics.

1.2. Assigning generic functions to variables

Ordinary functions can be assigned directly to variables:

var n1 = 10
var n2 = 20
func swapValues(_ a: inout Int, _ b: inout Int) {
    (a, b) = (b, a)
}
var fn = swapValues
fn(&n1, &n2)
Copy the code

However, a generic function cannot be assigned to a variable as a normal function, otherwise an error will be reported.

The right thing to do (specify the type of the generic after a variable) :

var n1 = 10
var n2 = 20
func swapValues<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}
var fn: (inout Int, inout Int) -> () = swapValues
fn(&n1, &n2)
Copy the code

Multiple generic sample code:

Func test < T1, T2 > _ a: T1, _ b: (T2) {print (" a = \ (a), b = \ "(b))} the test (10, 20.0)Copy the code

Generic types

Structures and classes can also add generics, which are called generic types.

Class 2.1.

Sample code (stack: First in, last out) :

<E> {var elements = [E](); Func pop() -> E {calls.removelast ()} func top() -> E Func size() -> Int {elements. Count}} var intStack = Stack<Int>() // Var stringStack = Stack<String>() var anyStack = Stack<Any>()Copy the code

Why is it ok to use a class with a type after its name, but not a function? This is because the type of the generic type is already specified when the function takes an argument.

2.2. Inheritance

Classes that inherit from generic types must also be subclasses of generic types.

class SubStack<E>: Stack<E> {}
Copy the code

2.3 structure

If a struct body function needs to modify the structure’s memory (modify storage attributes), it must be preceded by mutating.

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
    }
}
Copy the code

2.4. The enumeration

Enum Score<T> {case point(T) case grade(String)} let Score<Int>.point(100) // Let score1 = Score. Point (100.0) let score2 = Score<Double>.grade("A")Copy the code

The nature of generics

Sample code:

func swapValues<T>(_ a: inout T, _ b: Inout T) {(a, b) = (b, a)} var i1 = 10 var i2 = 20 swapValues (& i1, i2) var = 10.0 var d1 d2 = 20.0 swapValues (& d1, &d2)Copy the code

The same function can take different types of arguments. Is it an overloaded function like C++ that generates many different types of arguments? If so, the memory address of the function with different parameter types must be different, let’s look at the assembly.

Assembly shows that the addresses of the two functions are the same.

Question: how to achieve parameter interactive assignment when the type of parameter passed is different (memory layout is different)?

As you can see from the above assembly, the function passes another metatype information parameter in addition to the two external parameters. The inside of the function is to get what the actual type of the argument is based on the metatype argument.

4. Association type

Associated Type defines a placeholder name for the Type used in the protocol.

Sample code (stack) :

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

The associatedType Element represents the definition of a generic type named Element.

When using an association type, there are two ways to let the compiler know the definite type of the association type:

  • Format:Typealias Association type = True typeExample:typealias Element = String
  • The second approach is to change the implementation protocol parameters directly to deterministic types.

Use association types:

class StringStack: Stackable {
    // typealias Element = String
    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
    }
}
Copy the code

When the class implementing the protocol is also unable to determine the association type:

class Stack<E>: Stackable {
    // typealias Element = 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
    }
}
Copy the code

A protocol can have multiple association types:

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

Cannot concatenate association types together for declaration, otherwise an error is reported.

Type constraint

Generics can be restricted/constrained.

Example code 1:

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

In the example above, T is constrained by Person and Runnable, meaning that T can only be a Person class and must comply with the Runnable protocol.

Example code 2:

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 s1 = Stack<Int>()
var s2 = Stack<Int>()
var s3 = Stack<String>()
equal(s1, s2)
equal(s2, s3)
Copy the code

Example code 2 is a little more complicated, with additional conditions added after constraining generics. S1 and S2 must not only comply with Stackable protocol, but S1 and S2 must be equal, and S1 must comply with Hashable protocol.

Equal (s1, s2) compiles because generics are ints. But equal(s2, s3) will compile an error because s2 generics are Int and S3 generics are String.

Extension: Both Int and String are Hashable compliant.

Note:

Sample code:

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

When using R1, the compiler considers r1 to be of type Runnable. Because the specific type of return is not known until the program runs.

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}} func get(_ type) Int) -> Runnable { if type == 0 { return Person() } return Car() }Copy the code

Compiling will result in an error:agreementRunnableCannot be used as a generic constraint because it hasSelfOr associative type constraints

The reason for the error is that the program does not know at compile time what type the protocol association type Speed is.

There are two solutions: Solution one: use 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

The return value is generic. When defining a variable, the type of the variable is specified, so the return value of the function is determined accordingly.

Opaque type Use some keyword to declare an opaque type.

An error is reported even if the opaque type is qualified before the return value of the function, because opaque types restrict the function to returning only one type.

Thought 1: Why do I only need to qualify opaque types?

Because opaque types restrict a function to returning only one type, the function already knows internally what type to return.

Thought 2: Since you can only return one type, why not just return the concrete type?

The exact type of the return value can be hidden from the outside world (that is, the real type is masked).

Application scenario: When you need to return an object that complies with a protocol, you can use the opaque type only when you want to expose the interface defined in the protocol.

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 pet attribute in the code above hides the real type of the return value (if there is no associated type, you can hide the real type without adding some).

6. The nature of alternatives

The essence of the optional is enumerated types.

Enumeration definition:

public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
    public init(_ some: Wrapped)
}
Copy the code

Sample code:

var age: Int? = 10
age = 20
age = nil
Copy the code

The complete code form for the above sample code:

var age: Optional<Int> = Optional(10)
// var age: Optional<Int> = .some(10)
age = .some(20)
age = .none
Copy the code

? It’s optional grammar sugar.

Optional in switch:

var age: Int? = 10 age = 20 age = nil switch age { case let v? : print("1", v) case nil: print("2") }Copy the code

Normally, case let v, v must be an optional type, unlike if (which is automatically unpacked).

If thevFollowed by?The final resultvisIntType:

Equivalent code:

if let v = age {
    print("1", v)
} else {
    print("2")
}
Copy the code

Examples of multiple optionality:

Var age_: Int? = 10 var age: Int?? Var age0 = option. some(option. some(10)) age0 =.none Optional<Optional> =.some(.some(10)) age1 =.none // example 2 //var age: Int?? = 10 //// Example 2 Essence //var age0: Optional<Optional> = 10Copy the code

For more articles in this series, please follow our wechat official account [1024 Planet].