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.
callswapValues
I 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 type
Example: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:agreementRunnable
Cannot be used as a generic constraint because it hasSelf
Or 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 thev
Followed by?
The final resultv
isInt
Type:
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].