1, Currying

Swift can use Currying, which means that a method that takes more than one parameter can be converted into a method that takes the first parameter and returns a new method that takes the remaining parameters and returns a result

func add(_ v1:Int,_ v2:Int) -> Int {
    return v1 + v2
}
print(add (1, 2)) / / curry (Currying) func add (_ v: Int) - > (Int) - > Int {return {$0 + v}
}
print(add(1)(2))

Copy the code

2, mutating

Swift’s protocol can be implemented not only with class types, but also with structs and enums.

The Swift mutating keyword modifier is used to modify the struct or enum variable in the method, so if you do not write the mutating keyword in the protocol method, the other person uses the struct or enum to implement the protocol. You can’t change your variables in a method

When a class is used to implement a protocol with a mutating method, there is no need to add the mutating modifier in front of the implementation, because the class can change its member variables at will. So the mutating modifier in the protocol is completely transparent to the implementation of the class and can be treated as non-existent

protocol Vehicle {
   var numberOfWheels:Int{get}
   mutating func changeNumberOfWheels()
}

struct MyCar:Vehicle {
   var numberOfWheels: Int = 4
   
   mutating func changeNumberOfWheels() {
       numberOfWheels = 4
   }
}

class Cars: Vehicle {
   var numberOfWheels: Int = 0
   func changeNumberOfWheels() {
       numberOfWheels = 2
   }
}
Copy the code

3, the Sequence

A Sequence is a collection of values of the same type and provides the ability to iterate over these values.

The most common way to iterate over a Sequence is through a for-in loop

let animals = ["Antelope"."Butterfly"."Camel"."Dolphin"]
for animal in animals {
   print(animal)
}
Copy the code

Sequence protocol definition

protocol Sequence {
   associatedtype Iterator: IteratorProtocol
   func makeIterator() -> Iterator
}
Copy the code

The Sequence protocol has only one method that must be implemented, makeIterator()

MakeIterator () needs to return an Iterator, which is an IteratorProtocol type.

If you provide an Iterator, you can implement a Sequence. What is Iterator?

Iterator Iterator is the IteratorProtocol in the Swift 3.1 standard library. It is used to provide iteration capability for Sequence. For Sequence, we can use for-in to iterate over the elements, but behind the for-in is the IteratorProtocol

The IteratorProtocol is defined as follows:

public protocol IteratorProtocol {
   associatedtype Element
   public mutating func next() -> Self.Element?
}
Copy the code

For this for… In circulation

 let animals = ["Antelope"."Butterfly"."Camel"."Dolphin"]
for animal in animals {
   print(animal)
}
Copy the code

The compiler actually converts the above code into the following code

var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
    print(animal)
}
Copy the code
  • 1. Get an Iterator for the Animals array
  • 2. In a while loop, the Iterator gets the next element and operates on the element
  • 3. Exit the loop when next() returns nil

Implement a reverse order

Class ReverseIterator<T>: IteratorProtocol {typeAlias Element = T var array: [Element] var currentIndex = 0 init(array: [Element]) { self.array = array currentIndex = array.count - 1 } func next() -> Element? {if currentIndex < 0{
           return nil
       }
       else {
           let element = array[currentIndex]
           currentIndex -= 1
           returnStruct ReverseSequence<T>:Sequence {var array:[T] ReverseSequence (array: [T]) { self.array = array } typealias Iterator = ReverseIterator<T> func makeIterator() -> ReverseIterator<T> {return ReverseIterator(array: self.array)
   }
}

for item in ReverseSequence(array: animals){
   print(item)
}

Copy the code

Reference: Swift Sequence (1)

4. Tuples

Tuple is the only compound type in swift programming language, which can collate any type of a specified finite number into an object at a time. Each type in a tuple can be any structure, enumeration or class type, or even a tuple or empty tuple.

Swapping inputs, for example, is what ordinary programmers have probably written for ages

func swapMel1<T>(a:inout T, b:inout T) {
    let temp = a
    a = b
    b = temp
}
Copy the code

But with multivariate groups, we can swap without using extra space, and suddenly get to literary programmer writing

func swapMel2<T>(a:inout T, b:inout T) {
   (a,b) = (b,a)
}
Copy the code

5. Automatic closure (@autoclosure)

An automatic closure is a closure that is automatically created to package expressions passed to functions as actual arguments. It takes no actual arguments, and when it is called, it returns the value of the internally packaged expression

The nice thing about this syntax is that it lets you omit the parentheses surrounding functional formal arguments by writing plain expressions instead of explicit closures.

func getFirstPositive1(_ v1:Int, _ v2:Int) -> Int {
    return v1 > 0 ? v1 : v2
}
getFirstPositive1(1, 2)


func getFirstPositive2(_ v1:Int, _ v2:() -> Int) -> Int {
    returnv1 > 0 ? v1 : V2 ()} getFirstPositive2(1, 2) getFirstPositive2(1, {2}) func getFirstPositive3(_ v1:Int, _ v2:@autoclosure () -> Int) -> Int {return v1 > 0 ? v1 : v2()
}
getFirstPositive3(1, 2)
Copy the code
  • @autoClosure will automatically wrap 2 as {2}

  • @autoClosure only supports () -> T format arguments

??

In Swift, there is a very useful operator that can be used to quickly condition nil, which is?? . This operator evaluates the input and returns its value if the left value is non-nil Optional, and the right value if the left value is nil

?? It’s just an @autoclosure

public func ?? <T>(optional: T? , defaultValue: @autoclosure () throws -> T) rethrows -> TCopy the code

Let’s guess…? The implementation of the

func ?? <T>(optional: T? , defaultValue: @autoclosure () -> T) -> T { switch optional {case .Some(let value):
           return value
       case .None:
           return defaultValue()
       }
}
Copy the code

You may wonder why this is an autoclosure. It simply accepts T as the argument and returns it. Why would you want to wrap it in () -> T? In fact, this is one of the most laudable things about Autoclosure. If we use T directly, it means that in?? Before the operator can actually take a value, we have to have a default value passed into the method, which is usually not a big problem, but if the default value is computed through a bunch of complicated calculations, it can be a waste of time — because if optional isn’t nil, We don’t actually use the default value at all and return the unpacked value of optional. Such overhead is entirely avoidable by postponing the calculation of the default value until optional determines that it is nil

In the Swift, actually && and | | the two operators use the @ autoclosure

public static func && (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool

public static func || (lhs: Bool, rhs: @autoclosure () throws -> Bool) rethrows -> Bool
Copy the code

Escaping closures (@escaping) and non-escaping Closures (@Noescaping)

Escaping Closures (@escaping)

When a closure is passed to a function as an actual argument, the closure is said to have escaped because it was called after the function returned. When you declare a function that accepts closures as formal arguments, you can make it clear that closures are allowed to escape by writing @escaping before the formal argument.

One way closures can escape is if they are stored in variables defined outside the function. For example, many functions take the actual parameters of the closure as callbacks to start asynchronous tasks. The function returns after starting the task, but the closure does not complete until the task is complete — the closure needs to escape so that it can be called later

Example: closure that is invoked when the network request ends. The closure is executed some time after the request is made, not necessarily within the scope of the function

override func viewDidLoad() {
        super.viewDidLoad()
         
        getData { (data) in
            print("Closure returns result: \(data)")
        }
    }

    func getData(closure:@escaping (Any) -> Void) {
        print("Function starts execution --\(thread.current)")
        DispatchQueue.global().async {
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2, execute: {
                print("Closure executed --\(thread.current)")
                closure("345")})}print("End of function execution --\(thread.current)")}Copy the code

It can be seen from the results that the life of the escape closure is longer than that of the function.

The life cycle of an escape closure:

  • 1. Closures are passed to functions as arguments;
  • 2. Exit function;
  • The closure is called, and the closure life cycle ends

That is, the life of an escape closure is longer than that of a function. When the function exits, the reference to the escape closure is still held by other objects and will not be released at the end of the function.

Non-escaping closures (@Noescaping)

A function that takes a closure that is called before the function ends.

    override func viewDidLoad() {
        super.viewDidLoad()
         
        handleData { (data) in
            print("Closure returns result :\(data)")
        }
    }

    func handleData(closure:(Any) -> Void) {
        print("Function starts execution --\(thread.current)")
        print("Closure executed --\(thread.current)")
        closure("123")
        print("End of function execution --\(thread.current)")}Copy the code

Why are escape closures and non-escape closures separated

In order to manage memory, a closure will forcibly reference all objects it captures. For example, if you access the properties and functions of the current controller in the closure, the compiler will ask you to display a reference to self in the closure, so that the closure will hold the current object, leading to circular references.

A non-escape closure does not generate a circular reference, it is released within the scope of the function, and the compiler is guaranteed that the closure will release any objects it captures at the end of the function; Another benefit of using non-escape closures is that the compiler can apply more powerful performance optimizations. For example, when the life cycle of a closure is known, some retain and release calls can be eliminated. In addition, memory for the context of a non-escape closure can be stored on the stack rather than on the heap.

7. Operators

Unlike Objective-C, Swift supports features such as overloaded operators, which are probably most commonly used to define simple computations

System operator

Let’s say we need a data structure that represents a two-dimensional vector

struct Vector2D {
   var x:CGFloat = 0
   var y:CGFloat = 0
}
Copy the code

A very simple requirement is to add two Vector2D

letV1 = Vector2D(x: 2.0, y: 3.0)letVector2D(x: 1.0, y: 4.0) Vector2D(x: 1.0, y: 4.0)let v3 = Vector2D(x: v1.x + v2.x, y: v1.y + v2.y)  
Copy the code

It might be fine if you just do it once, but in general we do a lot of this. In this case, we might prefer to define a Vector2D addition operation to keep the code simple and clear

func +(left:Vector2D,right:Vector2D) -> Vector2D {
   Vector2D(x: left.x + right.x, y: left.y + right.y)
}
let v3 = v1 + v2
Copy the code

8. Custom operators

In Swift language, common operators have +, -, *, /, >, <, = =, &&, | |, and so on, if you don’t like, you can also define their own operators.

  • precedencegroup: Defines the precedence of the operator
  • associativityAssociative property of the: operator
  • higherThan,lowerThan: The priority of the operator
  • Prefix, infix, postfix: prefix, infix, suffix operators

infix

Precedencegroup MyPrecedence {// higherThan: AdditionPrecedence lowerThan: AdditionPrecedence > AdditionPrecedence > AdditionPrecedence > associativity: Nonefalse                   // true= assignment operator,false= non-assignment operator} infix operator +++: MyPrecedence // Inherits the MyPrecedence group // infix operator +++: AdditionPrecedence func +++(left: Int, right: Int) -> Int {AdditionPrecedence func +++(left: Int, right: Int)return left+right*2
}
 
print(2 + + + 3) / / 8Copy the code

The prefix

prefix operator ==+
prefix func ==+(left: Int) -> Int {
   
   return left*2
}
print(= = + 2) / / 4Copy the code

The suffix

postfix operator +==
postfix func +==(right: Int) -> Int {
   
   return right*3
}
print/ / 6 (2 + = =)Copy the code

9,inout: Input and output parameters

A variable form parameter can only be changed inside a function. If you want a function to be able to change the value of a formal parameter, and you want those changes to persist after the function ends, you need to define formal parameters as input and output formal parameters.

Adding an inout keyword at the beginning of the formal parameter definition defines an input/output formal parameter. An input/output formal parameter has a value that can be input to a function, modified by the function, and printed out to replace the original value.

You can only treat variables as actual parameters of input and output formal parameters. You cannot use constants or literals as actual arguments because constants and literals cannot be modified. When passing a variable as an actual argument to an input/output formal argument, add an ampersand (&) to make it clear that it can be modified by the function.

var b = 10
func test(a:inout Int) {
   a = 20
}
test(a: &b)
print(b) //20
Copy the code

You can use inout to define an input and output parameter: you can modify the values of external arguments inside the function

  • 1. Immutable parameters cannot be marked asinout
  • 2,inoutParameters cannot have default values
  • 3,inoutArguments can only be passed in values that can be assigned more than once
  • 4,inoutThe essence of a parameter is address passing

10, the subscript

Subscripts are familiar. Using subscripts to read and write data structures such as arrays or dictionaries seems to be the industry standard in most languages. In Swift, Array and Dictionary of course also implement subscript reading and writing

Var arr = [1, 2, 3] arr [2] / / 3 arr [2] = 4 / / arr = var dic = [[1, 4-trichlorobenzene]"cat":"meow"."goat":"mie"]
dic["cat"]          // {Some "meow"}
dic["cat"] = "miao" // dic = ["cat":"miao"."goat":"mie"]  
Copy the code

As a language that represents advanced productivity, Swift allows us to customize subscripts, and we found the subscript types that Array already supports

subscript (index: Int) -> T
subscript (subRange: Range<Int>) -> Slice<T>
Copy the code

We find that if we want to extract the 0, 2, 4 subscripts, we need to iterate through the enumeration.

A better way to do this is to implement a reading method that takes an array as a subscript input

extension Array {
   subscript(input: [Int]) -> ArraySlice<Element> {
       get {
           var result = ArraySlice<Element>()
           for i in input {
               assert(i < self.count, "Index out of range")
               result.append(self[i])
           }
           return result
       }
       set {
           for (index,i) in input.enumerated() {
               assert(i < self.count, "Index out of range")
               self[i] = newValue[index]
           }
       }
   }
}

var arr = ["a"."b"."c"."d"."z"]
print[[0, 3]] (arr) / / /"a"."d"]
Copy the code

11. Nested functions

We can use functions as arguments or variables, with functions nested inside

func forward(_ forward:Bool) -> (Int) -> Int { func next(_ input:Int) -> Int { input + 1 } func previous(_ input:Int) ->  Int { input - 1 }return forward ? next : previous
}
Copy the code

namespaces

One of objective-C’s longstanding complaints is the lack of namespaces, as all code and referenced static libraries end up being compiled into the same domain and binary during application development. The result is compile-time conflicts and failures if we have duplicate class names. To avoid this, objective-C types are prefixed with two or three letters, such as Apple’s NS and UI prefixes, SK (StoreKit), CG (CoreGraphic), and so on. Most developers in the Objective-C community follow this convention, prefixing their abbreviations with libraries like AFNetworking or MBProgressHUD. This solves part of the problem, at least when we directly refer to libraries from different people, the probability of conflicts is greatly reduced, but prefixes do not mean that there are no conflicts, and sometimes we do have the same situation even if we use prefixes. On the other hand, you might want to use two different libraries that reference the same popular third-party library in each of them without changing the name. This is fine if you use one of these libraries separately, but once you add both libraries to your project, the shared third-party library conflicts with itself.

In Swift, because namespaces are available, even types with the same name can coexist peacefully as long as they come from different namespaces. Unlike C#, which explicitly specifies namespaces in files, Swift’s namespaces are based on modules rather than explicitly specified in code, with each module representing a namespace in Swift. That is, the type names in the same target cannot be the same. By default, the content added to the main target of the app is in the same namespace when developing the app. We can create a new Module by creating the target of the Cocoa (Touch) Framework. This allows us to add a type with the same name to two different targets

13. Typealias Alias

We can give a complex and difficult to understand type an alias to make it easier to use and understand

The swift library defines Void as an empty tuple

public typealias Void = ()
Copy the code

We know that there are no byte, short, or Long types in Swift, but if we want such types, we can use TypeAlias

typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64
Copy the code

We can also give the function an alias

typealias IntFn = (Int,Int) -> Int
func difference(v1:Int,v2:Int) -> Int {
   v1 - v2
}
let fn:IntFn = difference
print(fn (2, 1)) / / 1Copy the code

We can also alias tuples

typealias Date = (year:Int,month:Int,day:Int)
func test(_ date:Date) {
   print(date.year)
}
test(,10,30) (2019)Copy the code

14, associatedtype

We can define properties and methods in the Swift protocol and require that they be implemented according to the protocol type:

protocol Food { }

protocol Animal {
   func eat(_ food: Food)
}

struct Meat: Food { }
struct Grass: Food { }
Copy the code
struct Tiger: Animal {
   func eat(_ food: Food) {

   }
}
Copy the code

Since tigers are not vegetarian, we probably need to do some conversion work to use meat in Tiger’s Eat

A colon can be used in the AssociatedType declaration to specify that the type satisfies a protocol

protocol Animal {
   associatedtype F: Food
   func eat(_ food: F)
}

struct Tiger: Animal {
   func eat(_ food: Meat) {
       print("eat \(meat)")
   }
}

struct Sheep: Animal {
   func eat(_ food: Grass) {
       print("eat \(food)")}}Copy the code

However, with the addition of AssociatedType, the Animal protocol cannot be used as a separate type. Imagine that we have a function to determine whether an animal is dangerous:

func isDangerous(animal: Animal) -> Bool {
   if animal is Tiger {
       return true
   } else {
       return false}}Copy the code

complains

Protocol ‘Animal’ can only be used as a generic constraint because it has Self or associated type requirements

This is because Swift needs to determine all types at compile time, and since Animal contains an indeterminate type, the F of Animal is indeterminate as it changes from one type to another. You will not be able to specify the type of the eat argument. When a protocol adds a constraint such as AssociatedType or Self, it can only be used as a generic constraint, not as a placeholder for independent types, and loses the dynamic dispatch feature. In other words, in this case, we need to rewrite the function to be generic

func isDangerous<T: Animal>(animal: T) -> Bool {
   if animal is Tiger {
       return true
   } else {
       return false
   }
}

isDangerous(animal: Tiger()) // true
Copy the code

15. Variable parameters

A variable form parameter can accept zero or more values of a specific type. The variable parameter must be of the same type. When calling a function, you can use a variable formal parameter to declare that the formal parameter can be passed a variable number of values. You can do this by inserting three dots (…) after the type name of a formal parameter. To write variable form parameters.

func sum(_ numbers:Int...) -> Int{
   var total = 0
   for item in numbers {
       total += item
   }
   return total
}
Copy the code

Swift’s own print function

/// - Parameters:
///   - items: Zero or more items to print.
///   - separator: A string to print between each item. The default is a single
///     space (`""`).
///   - terminator: The string to print after all items have been printed. The
///     default is a newline (`"\n"`).
public func print(_ items: Any... , separator: String ="", terminator: String = "\n")
Copy the code
  • 1, the first parameter: is the value to print, is a variable parameter
  • 2. The second argument: the place where the two print values are connected. The default is a space
  • 3, the third argument: the end of the default is \n newline

16. Initialization

Initialization is the process of preparing an instance for a class, structure, or enumeration. This process requires setting an initial value for each storage property in the instance and performing any other necessary configuration or initialization before the new instance can be used

You do this by defining an initializer, which is more like a special method for creating new instances of a particular type. Unlike objective-C initializers, Swift initializers do not return values. The primary role of these initializers is to ensure that new instances of a type are correctly initialized before being used for the first time.

Classes have two types of initializers

  • 1. Designated Initializer
  • 2. Convenience Initializer
class Person { var age: Int var name: Init (age:Int, Name :String) {self.age = age self.name = name} // Convenience init(age:Int){self.init(age:age,name:"")}}Copy the code
  • 1. Every class has at least one designated initializer. The designated initializer is the primary initializer of the class
  • 2. The default initializer is always the class’s designated initializer
  • 3. Classes tend to have a small number of designated initializers; a class usually has only one

Rules for calling each other to initializers

  • The specified initializer must be called from its immediate parent
  • 2. The convenience initializer must call another initializer from the same class
  • 3. The convenience initializer must eventually call a specified initializer

17, Static & Class

The concept of type scope in Swift has two different keywords, static and class. That’s exactly what both of these words mean

In the context of non-class types, we use static to describe type scope uniformly

18. Default parameter

Swift’s methods support default parameters, which means that you can assign a default value to a parameter when declaring a method. If this parameter is passed in when the method is called, the value is used; if the input parameter is missing, the default value is used

func test(a:String = "1",b:String,c:String = "3") {}Copy the code

19. Match patterns

What are patterns patterns are rules for matching, such as case of switch, catch of error, if guard, while for statement

The mode in Swift has

  • 1. Wildcard Pattern
  • 2. Identifier Pattern
  • 3. Value-binding Pattern
  • 4. Tuple Pattern
  • 5, Enumeration Case Pattern
  • 6. Optional Pattern
  • 7. Type-casting Pattern
  • 8. Expression Pattern

Wildcard Pattern

  • _Match any value
  • _?Matches non-nil values
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(letname,_?) :print("animal",name)
    default:
        print("other")
        break
    }
}

check(.human(name: "Xiao Ming", age: 20)) //human: check(. Human (name:"Little red", age: nil))//human: small red check(.animal(name:"dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
Copy the code

Identifier Pattern

Is to assign the corresponding variable constant

let a = 10
let b = "text"
Copy the code

Value Binding Pattern

letPoint = (2,3) switch point {case (let x,let y):
    print("x:\(x) y:\(y)")
}
//x:2  y:3

Copy the code

Tuple Pattern

Matches any primitive ancestor

letPoint = [(0, 0), (1, 0), (2, 0)]for (x,_) in point{
    print(x)
}
//0
//1
//2
Copy the code

Enumeration Case Pattern

The if case statement is equivalent to a case switch statement, simplifying some judgment statements

let age = 2
//if
if age >= 0 && age <= 9 {
    print("[0, 9]"} // Enumeration modeif case0... 9 = age{print("[0, 9]")}Copy the code
letages:[Int?] = [2, 3, nil, 5]for case nil in ages{
    print("Has nil value")}Copy the code

Optional Pattern

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

Is equivalent to

letages:[Int?] = [nil, 2, 3, nil]for item in ages{
    if let age = item {
        print(age)
    }
}

Copy the code

Type-casting Pattern

class Animal {
    func eat() {
        print(type(of: self),"eat")
    }
}

class Dog: Animal {
    func run() {
        print(type(of: self),"run")
    }
}

class Cat: Animal {
    func jump() {
        print(type(of: self),"jump")
    }
}

func check(_ animal:Animal) {
    switch animal {
    case let dog as Dog:
        dog.run()
        dog.eat()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
Copy the code

Expression Pattern

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

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)
    }
    
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print("> = 100")
case 90:print("> = 90")
case80.. < 90:print("[80, living]")
case60... 79:print("[60,]")
default:break
}
Copy the code
extension String{
    static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
        pattern(value)
    }
}

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



var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k") :print("Starts with a J, or ends with a K.")
default:
    break
}
Copy the code

20,… And.. <

The Range operator is used to simply specify a Range of consecutive counts from X to Y

We can take a closer look at Swift’s definition of the two operators

/// Forms a closed range that contains both `minimum` and `maximum`. func ... <Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos> /// Forms a closed range that contains both `start` and `end`. /// Requres: `start <= end` func ... <Pos : ForwardIndexTypewherePos : Comparable>(start: Pos, end: Pos) -> Range<Pos> /// Forms a half-open range that contains `minimum`, but not /// `maximum`. func .. <<Pos : ForwardIndexType>(minimum: Pos, maximum: Pos) -> Range<Pos> /// Forms a half-open range that contains `start`, but not /// `end`. Requires: `start <= end` func .. <<Pos : ForwardIndexTypewherePos : Comparable>(start: Pos, end: Pos) -> Range<Pos> /// Returns a closed interval from `start` through `end` func ... <T : Comparable>(start: T, end: T) -> ClosedInterval<T> /// Returns a half-open interval from `start` to `end` func .. <<T : Comparable>(start: T, end: T) -> HalfOpenInterval<T>Copy the code

As you can see, all of these methods support generics. In addition to our usual input Int or Double, which returns a Range, this operator also has an overload that accepts Comparable input and returns ClosedInterval or HalfOpenInterval

For example, if you want to make sure that all characters in a word are lowercase, you can do this

let test = "helLo"
let interval = "a"."z"
for c in test {
    if! interval.contains(String(c)) {print("\(c) Not lowercase"}} // Output // L is not lowercaseCopy the code

AnyClass, metatype and.self

In Swift, the concept of “Any” can be expressed in addition to Any and AnyObject, there is also an AnyClass. AnyClass is defined in Swift by a TypeAlias

public typealias AnyClass = AnyObject.Type
Copy the code

The result of anyObject. Type is a Meta. We always declare.type after the name of the Type, for example, a.type stands for the Type of Type A. That is, we can declare A meta-type to store the type A itself, and to retrieve its type from A, we need to use.self

In Fact, in Swift,.self can be used after a type to get the type itself, or after an instance to get the instance itself. The former method can be used to get a value representing the type, which can be useful at some point; And the latter because of the instance itself

class A {
    class func method() {
        print("Hello")}}let typeA: A.Type = A.self
typeArjun ethod () / / orlet anyClass: AnyClass = A.self
(anyClass as! A.Type).method() 
Copy the code

What’s the point, you may ask, if we can’t just call a.method ()? Yes, we don’t need to care about the metatype of a single type at all, but the concept of metatype, or metaprogramming, can be very flexible and powerful, which comes in handy when we’re writing framework code. For example, when we want to pass some type, we don’t have to constantly change the code. In the example below, we get the MusicViewController and AlbumViewController meta-types declaratively, but this could have been done by reading in a configuration file or something. Metatype programming is hard to replace by storing these metattypes in arrays and passing them on to other methods for configuration

class MusicViewController: UIViewController {

}

class AlbumViewController: UIViewController {

}

let usingVCTypes: [AnyClass] = [MusicViewController.self,
    AlbumViewController.self]

func setupViewControllers(_ vcTypes: [AnyClass]) {
    for vcType in vcTypes { 
            if vcType is UIViewController.Type {
            let vc = (vcType as! UIViewController.Type).init()
            print(vc)
        }

    }
}

setupViewControllers(usingVCTypes) 
Copy the code

In this way, we can build a framework and configure it in a DSL way to perform complex operations without touching the Swift code

22. Dynamic types

In Swift we can use dynamicType to get the dynamicType of an object (that is, the actual type at runtime, not the type specified by code or seen by the compiler). However, in use, Swift does not currently support multi-methods, that is, it cannot make appropriate overloaded method calls based on the type of the object when it is in motion

class Pet {}
class Dog:Pet {}
class Cat:Pet {}
 
func eat(_ pet:Pet) {
    print("pet eat")
}

func eat(_ dog:Dog) {
    print("dog eat")
}

func eat(_ cat:Cat) {
    print("cat eat")
}
 
func eats(_ pet:Pet,_ cat:Cat) {
    eat(pet)
    eat(cat)
}
eats(Dog(), Cat())
//pet eat
//cat eat
Copy the code

Instead of printing the Dog type information to select the appropriate func eat(_ Dog :Dog) {} method at runtime, we ignored it and adopted the Pet method determined at compile time.

Because Swift does not dispatch dynamically by default, method calls can only be determined at compile time

To get around this limitation, we may need to perform judgments and conversions on input types

func eats(_ pet:Pet,_ cat:Cat) {
    if let aCat = pet as? Cat {
        eat(aCat)
    }else if let aDog = pet as? Cat{
        eat(aDog)
    }
    eat(cat)
}
Copy the code

23, attributes,

The properties declared in Swift include

  • 1. Storage properties: Storage properties will actually allocate the address in memory for the storage of properties
  • 2, computing properties: computing properties do not include storage, only providesetandgetmethods

Storage properties

We can provide both willSet and didSet property observation methods in storage properties

class Person {
    var age:Int = 0{
        willSet{
            print("About to set age from \(age) to \(newValue)")
        }
        didSet{
            print("Age has been set from \(oldValue) to \(age)")}}}letP = Person() p.age = 10 // About to set age from 0 to 10 // Already set age from 0 to 10Copy the code

In willSet and didSet we can use newValue and oldValue to get the value to be set and the value to be set, respectively.

The setting of the property by the initializer method and the resetting of the property in willSet and didSet do not trigger the property observation call again

Calculate attribute

Attributes declared in Swift include storage attributes and computed attributes. Storing attributes will actually allocate the address in memory to store the attributes, while calculating attributes does not include the storage behind, but only provides two methods of SET and GET. Attribute observation and evaluation attributes cannot coexist in the same type. That is, it is impossible to have both set and willSet or didSet in a property definition.

We can do the same thing for willSet and didSet by rewriting set. If we can’t change the class and want to do something with property observation, we may need to subclass the class and override its properties

Overridden attributes do not know the specific implementation of the parent class attributes, but only inherit the name and type from the parent class attributes, so in the overridden attributes of the child class we can arbitrarily add attributes to observe the parent class attributes, regardless of whether the parent class is storing attributes or calculating attributes

class A {
    var number:Int {
        get{
            print("get")
            return1}set{
            print("set")
        }
    }
}

class B:A {
    override var number: Int{
        willSet{
            print("willSet")
        }
        didSet{
            print("didSet")}}}let b = B()
b.number = 10

get
willSet
set
didSet
Copy the code

The calls to set and the corresponding property observations are expected. The thing to notice here is that GET is called first. This is because we implemented didSet, and oldValue is used in didSet, and that value needs to be retrieved and stored for later before the entire set action, otherwise it’s not guaranteed to be correct. If we don’t implement didSet, this get operation won’t exist either.

24, Lazy modifier

Delayed loading or delayed initialization is a common optimization method. When building and generating new objects, memory allocation will take a lot of time at runtime, especially if the properties and contents of some objects are very complex. In addition, some cases do not use all attributes of an object at once, and by default, storage attributes that are not used in a particular environment are also initialized and assigned, which is a waste of time

Swift provides a keyword lazy

class A {
    lazy var str:String = {
        let str = "Hello"
        print("Output on first access")
        return str
    }()
}

let a = A()
print(a.str)
Copy the code

For simplicity, if we don’t need to do any extra work, we can just write the assignment to the lazy property, right

lazy var str1 = "word"
Copy the code

Reflection and Mirror

The term “reflection” is not often used in Objective-C because the Objective-C runtime is much more flexible and powerful than normal reflection. Things that many readers are used to, like generating a class or selector from a string and then generating an object or calling a method, are actually concrete representations of reflection. And in Swift, even if you put aside the Objective-C runtime, there’s reflection in the pure Swift category, but it’s much weaker.

Swift provides Mirror types for mapping

struct Person {
    let name:String
    let age:Int
}

let xiaoming = Person(name: "Xiao Ming", age: 10)
let r = Mirror(reflecting: xiaoming)

print("Xiaoming is \ (r.d isplayStyle)")
print("Number of attributes: \(r.dren. Count)")

for child in r.children{
    print("Attribute name :\(child.label) Value :\(child.value)")} xiaoming is Optional (Swift. Mirror. DisplayStyle. Struct) attribute number: 2 attribute name: (Optional"name"Attribute name :Optional("age") 值:10
Copy the code

The descriptions of the elements contained in the result of the Mirror initialization are grouped under the children attribute

public typealias Child = (label: String? , value: Any) public typealias Children = AnyCollection<Mirror.Type.Child>Copy the code

If printing one by one is too much hassle, we can simply dump an object by taking a mirror image of it and printing it out as standard output

print(dump(xiaoming)) ▿ reflection. Person - name:"Xiao Ming"
  - age: 10
Person(name: "Xiao Ming", age: 10)
Copy the code

A common application scenario is to do valueForKey: like KVC in Objective-C for objects of type Swift. We compare the name of the property to the key value we want to get. It’s very simple

func valueFrom(_ object: Any, key: String) -> Any? {
    let mirror = Mirror(reflecting: object)
    for child in mirror.children {
        let (targetKey, targetMirror) = (child.label, child.value)
        if key == targetKey {
            return targetMirror
        }
    }
    return nil
}


if let name = valueFrom(xiaoming, key: "name") as? String {
    print("Get value by key: \(name)"} get the value by key: xiaomingCopy the code

In the current version, Swift’s reflection is not very powerful, and we can only read properties, not set them. It should also be noted that while it is theoretically possible to apply reflective features to actual app production, this set of mechanisms was originally designed for output in the REPL environment and Playground. So it’s best to stick to Apple’s rule and only use it for deep exploration of an object on the REPL and Playground, and avoid using it in app making — because you never know when they’re going to break down or be changed too much

26. (optional)

Click through to the official documentation and you can see that Optional is an enumeration

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

Let’s see if these two values are the same anotherString literalString

var string:String? = "string"
var anotherString:String?? = string
var literalString:String?? = "string"

print(anotherString)
print(literalString)
print(anotherString == literalString)

//Optional(Optional("string"))
//Optional(Optional("string")) / /true
Copy the code
  • 1.var anotherString:String?? = stringAnd the way I write it is thatOptional.some(string)
  • 2,var literalString:String?? = "string"The writing isOptional.some(Optional.some("string"))

They return the same value, so they’re equal

Are the next two objects equal?

var aNil:String? = nil
var anoterNil:String?? = aNil
var literalNil:String?? = nil
print(anoterNil)
print(literalNil)
print(anoterNil == literalNil)
//Optional(nil)
//nil
//false
Copy the code
  • 1.anoterNilYes optional contains an optional value fornilThe optional
  • 2,literalNilYes The optional value isnilThe optional

We can print specific information using FR V-R

27. Match patterns

What are patterns patterns are rules for matching, such as case of switch, catch of error, if guard, while for statement

The mode in Swift has

  • 1. Wildcard Pattern
  • 2. Identifier Pattern
  • 3. Value-binding Pattern
  • 4. Tuple Pattern
  • 5, Enumeration Case Pattern
  • 6. Optional Pattern
  • 7. Type-casting Pattern
  • 8. Expression Pattern

Wildcard Pattern

  • _Match any value
  • _?Matches non-nil values
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(letname,_?) :print("animal",name)
    default:
        print("other")
        break
    }
}

check(.human(name: "Xiao Ming", age: 20)) //human: check(. Human (name:"Little red", age: nil))//human: small red check(.animal(name:"dog", age: 5))//animal dog
check(.animal(name: "cat", age: nil))//other
Copy the code

Identifier Pattern

Is to assign the corresponding variable constant

let a = 10
let b = "text"
Copy the code

Value Binding Pattern

letPoint = (2,3) switch point {case (let x,let y):
    print("x:\(x) y:\(y)")
}
//x:2  y:3

Copy the code

Tuple Pattern

Matches any primitive ancestor

letPoint = [(0, 0), (1, 0), (2, 0)]for (x,_) in point{
    print(x)
}
//0
//1
//2
Copy the code

Enumeration Case Pattern

The if case statement is equivalent to a case switch statement, simplifying some judgment statements

let age = 2
//if
if age >= 0 && age <= 9 {
    print("[0, 9]"} // Enumeration modeif case0... 9 = age{print("[0, 9]")}Copy the code
letages:[Int?] = [2, 3, nil, 5]for case nil in ages{
    print("Has nil value")}Copy the code

Optional Pattern

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

Is equivalent to

letages:[Int?] = [nil, 2, 3, nil]for item in ages{
    if let age = item {
        print(age)
    }
}

Copy the code

Type-casting Pattern

class Animal {
    func eat() {
        print(type(of: self),"eat")
    }
}

class Dog: Animal {
    func run() {
        print(type(of: self),"run")
    }
}

class Cat: Animal {
    func jump() {
        print(type(of: self),"jump")
    }
}

func check(_ animal:Animal) {
    switch animal {
    case let dog as Dog:
        dog.run()
        dog.eat()
    case is Cat:
        animal.eat()
    default:
        break
    }
}
check(Dog())
//Dog run
//Dog eat
check(Cat())
//Cat eat
Copy the code

Expression Pattern

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

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)
    }
    
}
var stu = Student(score: 81, name: "tom")
switch stu{
case 100:print("> = 100")
case 90:print("> = 90")
case80.. < 90:print("[80, living]")
case60... 79:print("[60,]")
default:break
}
Copy the code
extension String{
    static func ~=(pattern:(String) -> Bool,value:String) ->Bool{
        pattern(value)
    }
}

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



var str = "jack"
switch str {
case hasPrefix("j"),hasSuffix("k") :print("Starts with a J, or ends with a K.")
default:
    break
}
Copy the code

28. Property access control

The five access levels in the Swift code are related to the source file that defines the entity and the module to which the source file belongs. Open has the highest access level, private the lowest, and internal is the default access level.

  • The open and public: Makes an entity available in the source file of the module it defines, or in the source file of another module by importing the module it defines. whilepublicModified classes or class members can only be inherited or overridden by subclasses in the current module; whileopenCan be inherited in another module or overridden by subclasses. Commonly used when defining a framework’s public interfaceopenandpublic;
  • internal: Enables entities to be used in any source file of the module they define, but not in the source file of other modules. Commonly used when defining the internal structure of an application or frameworkinternal.
  • fileprivate: makes an entity usable only in the source file it defines. When these details are used throughout the file, usefileprivateHide implementation details for a particular feature block.
  • private: makes an entity usable in the class it declares, and in extensions of that declaration in the same file. Is used when these details are only used in a single declarationprivateTo hide the implementation details of a particular feature.

Open can be accessed in different modules, public can only be accessed in the current module, internal system default access control rights, Fileprivate can be inherited but cannot be modified or overridden by subclasses, and private cannot be inherited

29. Tail recursion

Recursion is a useful method in programming to translate and describe complex processes in a way that is easy to understand. For example, if we want to write a function that adds from 0 to n, we can do it recursively if we don’t know the formula for summing arithmetic sequences:

func sum(_ n:Int) -> Int {
    if n == 0 {
        return0}return n + sum(n - 1)
}

sum(1000000)
Copy the code

Thread 1: EXC_BAD_ACCESS (code=2, address= 0x7FFeEF3ffff8

This is because every recursive call to sum needs to keep the current state on the call stack, otherwise we wouldn’t be able to compute the final n + sum(n-1). When n is large enough and the call stack is deep enough, the stack space will be exhausted and an error will result, which is often called stack overflow.

Tail recursion in general for recursion, a good way to solve stack overflow is to write tail recursion. Tail recursion, as the name implies, is the form in which the last action in a function is a function call whose return value is directly returned by the current function, avoiding storing state on the stack

func tailSum(_ n: Int) -> Int {
    func sumInternal(_ n: Int, current: Int) -> Int {
        if n == 0 {
            return current
        } else {
            return sumInternal(n - 1, current: current + n)
        }
    }

    return sumInternal(n, current: 0)
}

print(tailSum(1000000))

Copy the code

However, you will still get an error if you try to run this code directly in your project because the Swift compiler does not optimize tail recursion in Debug mode. We can change the Run configuration from Debug to Release in scheme Settings and the code will work correctly.

conclusion

  • 1, the article is read Wang Wei (onevcat). “Swifter-Swift essential Tips (fourth edition) summary
  • 2. The demo address of the code in the article