The article is from collated notes on objccn. IO/related books. “Thank you objC.io and its writers for selflessly sharing their knowledge with the world.”

Swift, the new element

1. Mr Currie

Swift can take methods that take more than one parameter and use them as Currying, making them more flexible.

// Add 1 to the input number:
 func addOne(num: Int) -> Int {
    return num + 1
}

// This function is very limited
// You can define a generic function that takes the number to be added to the input number and returns a function
// The returned function will take the input number itself and then operate on it

 func addTo(_ addNum: Int)- > (Int) - >Int {
    return {
        num in
        return num + addNum
    }
}
var add10 = addTo(10)
print(add10(5)) / / 15
Copy the code
// An example of a size comparison
func greatThan(_ target: Int)- > (Int) - >Bool {
    return {
        num in
        return num > target
    }
}

var greatThan10 = greatThan(10)
print(greatThan10(5)) // false
print(greatThan10(15)) // true
Copy the code

Currization is a method of mass producing similar methods by currization of a method template to avoid writing a lot of duplicate code and to facilitate maintenance.

In Swift, selectors can only be generated using strings. This makes it difficult to refactor and impossible to check at compile time. But target-Action is also an important design pattern in Cocoa, and one possible solution is to use the Currization of methods.

 protocol TargetAction {
    func performAction(a)
}

struct TargetActionWrapper<T: AnyObject> :TargetAction {
    let action: (T) -> () -> ()
    func performAction(a)- > () {if let t = target {
            action(t)()
        }
    } 
}

enum ControlEvent {
    case TouchUpInside
    case ValueChanged
    // ...
}

class Control {
    var actions = [ControlEvent: TargetAction] ()func setTarget<T: AnyObject> (target: T.action: @escaping (T) -> () -> (),
                           controlEvent: ControlEvent) {
        actions[controlEvent] = TargetActionWrapper(target: target, action: action)
    }
    func removeTargetForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent] = nil
    }
    func performActionForControlEvent(controlEvent: ControlEvent) {
        actions[controlEvent]?.performAction()
    } 
}
Copy the code

2. Declare the protocol method as mutating

Swift’s protocol can be implemented by class, struct, and enum. When writing the protocol, consider whether to use mutating to modify the method, for example, mutating func myMethod(). The Swift mutating keyword modifier is used to modify struct or enum variables in the method. If the protocol is not mutating in the method, it cannot change its own variables in the method.

 protocol Vehicle
{
    var numberOfWheels: Int {get}
    var color: UIColor {get set}
    mutating func changeColor(a)
}
struct MyCar: Vehicle {
    let numberOfWheels = 4
    var color = UIColor.blue
    mutating func changeColor(a) {
        color = .red
    } 
}
Copy the code

MyCar will not compile if mutating is removed from the protocol definition.

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. The mutating modifier used in the protocol is completely transparent to the implementation of the class and can be treated as nonexistent.

3. Sequence

Swift, the for… In can be used on any type that implements a Sequence, and to implement a Sequence you first need to implement an IteratorProtocol. A reverse iterator and sequence can be written like this:

// define a type that implements the IteratorProtocol
// IteratorProtocol needs to specify a TypeAlias Element
// And provide a return Element? The method of next ()
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(a) -> Element? {
        if currentIndex < 0{
            return nil 
        } else {
            let element = array[currentIndex]
            currentIndex - = 1
            return element
        } 
    }
}

// Then we define the Sequence
// Similar to IteratorProtocol except that a TypeAlias Iterator is specified
// And provide a return Iterator? The method of makeIterator ()
struct ReverseSequence<T> :Sequence {
    var array: [T]
    
    init (array: [T]) {
        self.array = array
    }
    
    typealias Iterator = ReverseIterator<T>
    
    func makeIterator(a) -> ReverseIterator<T> {
        return ReverseIterator(array: self.array)
    }
}

let arr = [0.1.2.3.4]

// You can use for Sequence... In to loop through
for i in ReverseSequence(array: arr) {
    print("Index \(i) is \(arr[i])")}// Index 4 is 4
// Index 3 is 3
// Index 2 is 2
// Index 1 is 1
// Index 0 is 0
Copy the code

Dig in for… What does the in method do, expand it:

var iterator = arr.makeIterator()
while let obj = iterator.next() {
    print(obj) 
}
Copy the code

At the same time, methods like Map, Filter, and Reduce can be used, because the Protocol extension has been implemented:

 extension Sequence {
    func map<T> (_ transform: @noescape (Self.Iterator.Element) throws -> T) rethrows- > [T]
    func filter(_ isIncluded: @noescape (Self.Iterator.Element) throws -> Bool) rethrows- > [Self.Iterator.Element]
    func reduce<Result> (_ initialResult: Result._ nextPartialResult: @noescape (partialResult: Result.Self.Iterator.Element) throws -> Result) rethrows -> Result
    // ...
}
Copy the code

4. Diverse groups

// Swap inputs. A normal method would say:
 func swapMe1<T> (a: inout T.b: inout T) {
    let temp = a
    a=b
    b = temp 
}

// Use multiple groups:
 func swapMe2<T> (a: inout T.b: inout T) {
    (a,b) = (b,a)
}
Copy the code

In the Objective-C version of the Cocoa API there are quite a few places where you need to pass Pointers to get values. This is usually due to the compromise of having only one value returned in Objective-C. For example, CGRect has a helper method called CGRectDivide, which is used to cut a CGRect into two regions at a certain location. The specific definition and usage are as follows:

/* CGRectDivide(CGRect rect, CGRect *slice, CGRect *remainder, CGFloat amount, CGRectEdge edge) */
CGRect rect = CGRectMake(0.0.100.100);
CGRect small;
CGRect large;
CGRectDivide(rect, &small, &large, 20.CGRectMinXEdge);
Copy the code

The code above splits the rect of {0,0,100,100} into two parts, small for {0,0,20,100} and large for {20,0,80,100}. Because of the C language’s single return, methods are passed Pointers to fill in the needed parts.

Now in Swift, multiple groups are used to return both the partitioned part and the rest:

 extension CGRect {
    / /...
    func divided(atDistance: CGFloat.from fromEdge: CGRectEdge)
                    -> (slice: CGRect, remainder: CGRect)
    / /...
}

let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
let (small, large) = rect.divided(atDistance: 20, from: .minXEdge)
Copy the code

5. @ autoclosure and????

@Autoclosure is an amazing creation of Apple that is more like “hack” the language. What @autoclosure does is automatically wrap an expression into a closure. Sometimes the syntax can look really nice.

A method takes a closure and prints when the closure executes true:

func logIfTrue(_ block: () - >Bool) {
    if block() {
        print(true)
    }
}
logIfTrue({ return 1 < 2 })  // true
logIfTrue({ 1 < 2 }) // true
logIfTrue{ 1 < 2 } // true
Copy the code

You can change the method argument by adding the @autoclosure keyword in front of the argument name:

func logIfTrue(_ block: @autoclosure() - >Bool) {
    if block() {
        print(true)
    }
}
logIfTrue(1 < 2)  // true
Copy the code

Swift will automatically convert 1 < 2 to () -> Bool. So you’ve got something that’s easy to write, that’s clear.

In Swift,?? Is a very useful operator that you can use to quickly condition nil. This operator evaluates the input and returns its value if the value on the left is non-nil Optional and returns the value on the right if the value on the left is nil, as in:

var level: Int?
var startLevel = 1
var currentLevel = level ?? startLevel
Copy the code

?? There are two versions:

func ?? <T>(optional: T? , defaultValue:@autoclosure() - >T?). ->T?
func ?? <T>(optional: T? , defaultValue:@autoclosure() - >T) - >T
Copy the code

StartLevel () -> Int (); startLevel () -> Int (); 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

Why use Autoclosure? If T is used directly, then it means that?? Before the operator can actually take a value, it has to have a default value passed into the method, which might be a waste of time if it’s a complicated calculation — because if optional isn’t nil, it doesn’t actually use the default value at all, Instead, it returns the value unpacked from optional. Such overhead is entirely avoidable by postponing the calculation of the default value until optional determines that it is nil. The Optional and default values can be written elegantly, bypassing conditional judgments and casts.

@AutoClosure does not support writing with input parameters, meaning that only parameters that look like () -> T can be simplified using this feature. Because of the caller’s nature, be careful when writing a method that accepts @Autoclosure; use the full closure if it is ambiguous or misleading.

6. @escaping

Swift’s syntax lends itself well to functional programming, of which closures are one of the core concepts. Swift can define a function that takes a function as an argument, and it is common to use closures to pass this argument when called:

 func doWork(block: () - > ()) {
    block()
}
doWork {
    print("work")}Copy the code

By default, this closure hides the assumption that the contents of the block in the argument will be completed before doWork returns. Calls to blocks are synchronous behavior. To put a block in a Dispatch to be called after doWork returns, we need to prefix the block type with the @escaping tag to indicate that the closure “escapes” from the method:

 func doWorkAsync(block: @escaping() - > ()) {
    DispatchQueue.main.async {
    block() }
}
Copy the code

There are also some behavior differences when calling the two methods using closures. Closures can capture variables within them. For a closure with no escape behavior like the one in the doWork argument, because the scope of the closure does not extend beyond the function itself, there is no need to worry about holding self, etc., inside the closure. Accepting @escaping doWorkAsync is different. Because of the need to ensure that members within the closure are still valid, Swift forces self to be written explicitly if self and its members are referenced within the closure. Compare the differences between the two use cases below:

class S {
    var foo = "foo"
    func method1(a) {
        doWork {
            print(foo) 
        }
        foo = "bar" 
    }
    func method2(a) {
        doWorkAsync {
            print(self.foo)
        }
        foo = "bar"}}S().method1() // foo
S().method2() // bar
Copy the code

Callers in method1 don’t need to take self.foo’s holding into account and are fairly straightforward to use. The printout of foo outputs the original value foo. In method2, since the closure can escape, Swift forces self to be written as a reminder that self is held. So in this case, I’m letting the closure hold self, and the value that I’m printing is bar after method2 finally assigns to foo. If you don’t want to hold self in a closure, you can use the [weak self] format: weak self

 func method3(a) {
    doWorkAsync {
        [weak self] in
        print(self?.foo ?? "nil")
    }
foo = "bar" }
S().method3() // nil
Copy the code

If you define a method in a protocol or superclass that accepts @escaping as an argument, the corresponding method must also be declared @escaping in the implementation protocol and type or subclass of this superclass, otherwise the two methods are considered to have different function signatures:

 protocol P {
    func work(b: @escaping() - > ())
}
// It can compile
class C: P {
    func work(b: @escaping() - > ()) {
        DispatchQueue.main.async {
            print("in C")
        b() 
    }
// Failed to compile
class C1: P {
    func work(b: () - > ()) {
        // ...}}Copy the code

7. Optional Chaining

Using Optional Chaining can get rid of many unnecessary judgments and values, but you need to be careful of traps when using it. Optional Chaining is always possible to return nil, so everything you get by using Optional Chaining is Optional.

 class Toy {
    let name: String
    init(name: String) {
        self.name = name
    }
}
class Pet {
    var toy: Toy?
}
class Child {
    var pet: Pet?
}

// ...
let toyName = xiaoming.pet?.toy?.name
Copy the code

The last thing I call is name, and in Toy’s definition name is defined as a definite String instead of a String, okay? ToyName is actually a String, right? The type of. Is this due to the Optional Chaining in any one? ., and return nil, in which case, of course, you can only get nil.

In practice, most of the time you might want to use the Optional Binding to directly value code like this:

if let toyName = xiaoming.pet?.toy?.name {
    // It's wonderful that Xiao Ming has a pet and the pet also happens to have a toy
}
Copy the code
extension Toy {
     func play(a) {
         / /...}}// Defines an extension for Toy and a play() method to play with the Toy
// Play with toys if you have any
xiaoming.pet?.toy?.play()
Copy the code

In addition to Xiao Ming, there may be xiao Hong, Xiao Li, xiao Zhang and so on. In this case, you want to abstract out the string of calls and make a closure to make it easier to use. Pass a Child object to play with if there is a pet and the pet has a toy:

 // This is the error code
 let playClosure = {(child: Child) - > ()in child.pet?.toy?.play()}
Copy the code

This code doesn’t make sense! The problem is the call to play(). There is no return for play(), which means that the method returns Void (or a pair of parentheses (), which are equivalent). After Optional Chaining, we get an Optional result. That is, you should end up with something like this closure

let playClosure = {(child: Child) - > ()? in child.pet?.toy?.play()}
Copy the code

An Optional Binding can be used to determine whether the method was successfully called:

if let result: () = playClosure(xiaoming) { 
    print("So happy.")}else { 
    print("No toys to play with.")}Copy the code

8. Operators

Swift supports features such as overloaded operators, which are probably most commonly used to define simple calculations. For example, we need a data structure that represents a two-dimensional vector:

 struct Vector2D {
    var x = 0.0
    var y = 0.0
}

// Add two Vector2D:
let v1 = Vector2D(x: 2.0, y: 3.0)
let v2 = Vector2D(x: 1.0, y: 4.0)
let v3 = Vector2D(x: v1.x + v2.x, y: v1.y + v2.y)

// Override the plus operator:
 func +(left: Vector2D.right: Vector2D) -> Vector2D {
    return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
let v4 = v1 + v2
/ / v4 for {x3.0, y7.0}
Copy the code

The plus, minus, and minus signs defined above are operators that already exist in Swift, and all they do is transform the arguments to overload. There is one more thing to do if you want to define a brand new operator.

For example, the dot product is a very common operator in vector operations. It represents the sum of the product of the corresponding coordinates of two vectors. By definition, and referring to the overloaded operator method, select +* to represent the operation:

 func + * (left: Vector2D.right: Vector2D) -> Double {
    return left.x * right.x + left.y * right.y
}
Copy the code

But the compiler gives us an error :Operator implementation without matching Operator declaration

This is because the operator is not declared. Operators such as +, -, and * can be overridden directly because they are already defined in Swift. If you want to add a new operator, you need to declare it and tell the compiler that the symbol is actually an operator. Add the following code:

 precedencegroup DotProductPrecedence {
    associativity: none
    higherThan: MultiplicationPrecedence
}
infix operator + *: DotProductPrecedence
Copy the code
  • precedencegroup

Defines an operator priority level. Operator priorities are defined somewhat similarly to type declarations, in that an operator must belong to a particular priority. Some common operation precedence groups have been defined in Swift standard library, such as AdditionPrecedence and MultiplicationPrecedence. If there is no suitable precedence set for the operator, you need to specify the associative and precedence order yourself.

  • associativity

Defines the associative law, that is, the order of computation if multiple operators of the same kind appear sequentially. For example, the common addition and subtraction methods are left, which means that multiple additions occur in the same order from left to right (since addition is commutative, this order doesn’t matter, but subtraction is important). The result of the dot product is a Double, which is no longer used in combination with other dot products, so none.

  • higherThan

The priority of the operation, the dot product takes precedence over the multiplication. In addition to higherThan, lowerThan is also supported to specify a lower priority than some other group.

  • infix

Indicates that we want to define a middle operator, that is, before and after the input; Other modifiers include prefix and postfix.

With that in mind, it’s easy to take the dot product of vectors:

 let result = v1 + * v2 // Output is 14.0
Copy the code

Swift operators cannot be defined locally, because at least you want to be able to use your operators globally, otherwise the operators are meaningless. Operators from different modules can clash, which is a special concern for library developers. If the operators in the library conflict, the user cannot make the call by specifying the library name in the same way that the type name conflict is resolved. So when you overload or customize an operator, you should try to use it as a “shorthand” for some other method, rather than implementing a lot of logic or providing unique functionality. This way, even if a conflict occurs, the consumer can still use the library by calling the method name. Operators should also be named as clearly as possible to avoid ambiguity and possible misunderstanding. Because an unrecognized operator is risky and difficult to understand, this feature should not be abused.

9. Parameter modification of func

When declaring a Swift method, we do not specify the modifier before the parameter, but declare the parameter directly:

 func incrementor(variable: Int) -> Int {
    return variable + 1
}
Copy the code

If you want to do something with the added variable, but don’t want to introduce a new variable:

// Error code
func incrementor(variable: Int) -> Int {
    variable + = 1
    print(variable)
    return variable
}
Copy the code

Swift is actually a language that hates change. Everything that is possible is assumed to be immutable by default, which is declared with let. This not only ensures security, but also improves compiler performance. The same is true for method arguments, all of which are let by default without modifiers.

 func incrementor2(variable: Int) -> Int {
    var num = variable
    num + = 1
    return num
}
Copy the code

When you want to modify the input value directly inside the method, you can use inout to modify the parameter:

func incrementor(variable: inout Int) {
    variable + = 1
}

var luckyNumber = 7
incrementor(variable: &luckyNumber)
print(luckyNumber)
// luckyNumber = 8
Copy the code

Int is a value type, and you can’t directly change its address to point to a new value. For value types, inout is equivalent to creating a new value inside the function and then assigning that value to the & modified variable when the function returns, which is different from the behavior of reference types.

Note that parameter modification is transition-constrained, meaning that for calls across hierarchies, the same parameter modification needs to be uniform. For example, if you want to extend the above method to implement a +N operator that can add any number, you can write it like this:

 func makeIncrementor(addNumber: Int)- > ((inout Int) - > ()) {func incrementor(variable: inout Int) -> () {
        variable + = addNumber;
    }
    return incrementor;
}
Copy the code

The return of the outer makeIncrementor will also need to specify the modifier in front of the parameter type to match the internal definition, otherwise it will not compile.

10. Literal expressions

A literal is a value such as a specific number, string, or Boolean that directly identifies its type and assigns a value to a variable.

// // 3, Hello, true are called literals
let aNumber = 3
let aString = "Hello"
let aBool = true

// Array and Dictionary also use literals when using simple description assignments
let anArray = [1.2.3]
let aDictionary = ["key1": "value1"."key2": "value2"]
Copy the code

Swift provides a set of protocols that use literals to express specific types. For those types that implement the literal representation protocol, when providing literal assignments, you can simply “seamlessly match” the values to the corresponding types by assigning according to the rules defined in the protocol methods. These protocols include various native literals, which are often used in real development:

      ExpressibleByArrayLiteral
      ExpressibleByBooleanLiteral
      ExpressibleByDictionaryLiteral
      ExpressibleByFloatLiteral
      ExpressibleByNilLiteral
      ExpressibleByIntegerLiteral
      ExpressibleByStringLiteral
Copy the code

All literal representation protocols define a TypeAlias and the corresponding init method. Take ExpressibleByBooleanLiteral for example:

protocol ExpressibleByBooleanLiteral {
    typealias BooleanLiteralType
    init(booleanLiteral value: BooleanLiteralType)
}
   
// In this protocol, BooleanLiteralType is already defined in the Swift library:
typealias BooleanLiteralType = Bool
Copy the code

When you need to implement a literal expression yourself, you can simply implement the defined init method. For example, to implement a Bool of your own, do this:

enum MyBool: Int {
    case myTrue, myFalse
}
extension MyBool: ExpressibleByBooleanLiteral {
    init(booleanLiteral value: Bool) {
        self = value ? .myTrue : .myFalse
    }
}

let myTrue: MyBool = true
let myFalse: MyBool = false
myTrue.rawValue    / / 0
myFalse.rawValue   / / 1
Copy the code

BooleanLiteralType is the simplest form, such as ExpressibleByStringLiteral such a deal more complicated. Not only does this protocol resemble the Boolean case above, defining StringLiteralType and the initialization method that accepts it, but the protocol itself requires the implementation of the following two protocols:

ExpressibleByExtendedGraphemeClusterLiteral
ExpressibleByUnicodeScalarLiteral
Copy the code

These two protocols correspond to character clusters and literal representations of characters. Form and consistent, but in the realization of the need when ExpressibleByStringLiteral will these three init methods are implemented.

class Person {
    let name: String
    init(name value: String) {
        self.name = value
    }
}

class Person: ExpressibleByStringLiteral {
    let name: String
    init(name value: String) {
        self.name = value
    }
    required init(stringLiteral value: String) {
        self.name = value
}
    required init(extendedGraphemeClusterLiteral value: String) {
        self.name = value
}
    required init(unicodeScalarLiteral value: String) {
        self.name = value
} }
Copy the code

We add the required keyword in front of all protocol-defined init because of the completeness of the initialization method. Subclasses of this class need to be able to make similar literal representations to ensure type-safety.

In the above example there are many repetitions of self.name assignments. An improvement is to call the original init(name value: String) in these initializers by adding a convenience in front of them:

class Person: ExpressibleByStringLiteral {
    let name: String
    init(name value: String) {
        self.name = value
    }
    required convenience init(stringLiteral value: String) {
        self.init(name: value)
}
    required convenience init(extendedGraphemeClusterLiteral value: String) {
        self.init(name: value)
}
    required convenience init(unicodeScalarLiteral value: String) {
        self.init(name: value)
} }
let p: Person = "xiaoMing"
print(p.name)
/ / output:
// xiaoMing
Copy the code

In the Person example above, you didn’t use an extension to extend the class so that it could be assigned literals, as MyBool did, because you can’t define the required initialization method in Extension. Cannot add a literal expression to an existing non-final class.