function

review

The three items in order of importance are:

A function can be assigned to a variable like an Int or a String, can be used as an input parameter to another function, or as a return value from another function.

2. Functions can capture variables that exist outside their local scope.

3, There are two ways to create functions, one is to use the func keyword, the other is {}. In Swift, the latter is called a closure expression.

1. Functions can be copied to variables or used as inputs and outputs of functions

  • Swift, like many modern programming languages, treats functions as “first-class objects.”

2. Functions can capture variables that lie outside their scope

  • When a function references a variable that is outside its local scope, the variable is captured and will continue to exist, rather than being destroyed after going out of scope.
  • In programming terms, the combination of a function and its captured variable environment is called a closure.

3. Functions can be declared as closure expressions using {}

  • In contrast to func, closure expressions are anonymous; they are not given a name.

  • Closure expressions can be much more compact than func

[1.2.3].map { $0 * 2} / / minus [2]
Copy the code

Flexibility of functions

Function as data

  • Using functions as data in this way (for example, building arrays containing sorting functions at run time) takes the dynamic behavior of the language to a new level.

Function as proxy

You can get those callbacks by defining a protocol, then having the agent’s owner implement the protocol, and finally registering the agent itself as a proxy.

Cocoa style proxy

  • Mark the proxy property asweakVery common in practice, this convention makes memory management easy. Classes that implement proxy protocols do not need to worry about introducing reference loops.

Implement the agent on the structure

  • In a word: structs are not appropriate in the broker and protocol pattern.

Use functions, not proxies

  • If only one function is defined in the proxy protocol, it is perfectly possible to replace the proxy property with a property that stores the callback function.

Inout parameters and mutable methods

An inout argument holds a value passed to a function that can change the value and then pass out of the function and replace the original value.

  • Distinguish between lvalue and rvalue: Lvalue describes a memory address, which is short for “left value” because lcalues are expressions that can exist on the left side of an assignment statement. For example, array[0] is an Lvalue because it describes the memory location of the first element in the array. And rvalue describes a value. 2+2 is an rvalue that describes the value of 4. You can’t put 2+2 or 4 to the left of an assignment.

  • For inout parameters, you can only pass an lvalue because an rvalue cannot be modified. When you use inout in a normal function or method, you need to pass them in explicitly: that is, with an ampersand in front of each lvalue.

  • The compiler may optimize inout variables to be passed by reference rather than copied on incoming and outgoing. However, the documentation makes it clear that we should not rely on this behavior.

Nested functions and Inout

You can use the inout parameter in nested functions, and Swift guarantees that such use is safe.

func incrementTenTimes(value: inout Int) {
  func inc(a) {
  	value + = 1
  }
  for _ in 0..<10 {
    inc()
  }
}

var x = 0
incrementTenTimes(value: &x)
x / / 10
Copy the code

However, you cannot let this inout parameter escape. This makes sense because the value of inout is copied back before the function returns.

& does not mean inout case

  • Speaking of unsafe functions, you should be careful about another meaning of & : converting a function argument to an unsafe pointer. If a function acceptsUnsafeMutablepointerAs arguments, you can use andinoutParameters similar to the method in onevarI’m going to put the variable&Pass it. In this case, you are indeed passing a reference, or rather, a pointer.

attribute

  • There are two methods that are different from other ordinary complex methods: evaluating properties and subscripting operators. The computed property looks like a regular property, but it doesn’t use any memory to store its value. Instead, each time this property is accessed, the return value is computed in real time. Evaluating a property is really just a method, but its definition and calling convention are unusual.

Change observer

  • We can also implement willSet and didSet methods for properties and variables, which are called every time a property is set (even if its value hasn’t changed).
  • WilSet and didSet are essentially shorthand for a pair of properties: one is a private storage property that stores values; The other is the public computed property that reads the value, and the setter for this computed property does extra work before and/or after storing the value in the private property.
  • KVO uses the runtime feature of Obkective -c to dynamically add an observer to the setter of a class, which is not possible in Swift today, especially for value types. Swift property observation is a pure compile-time feature.

Deferred storage property

  • Lazy initialization of a value is a common pattern in Swift, and to define a lazy initialization attribute, Swift provides a dedicated keywordlazyTo define a lazy property. Note that the delay attribute can only be usedvarDefinition, because its initial value may remain unset after the initialization method completes. Swift has strict rules for let constants, which must have a value before the instance’s initialization method completes. Delay modifiers are programmingMemory,A concrete manifestation of.
  • Unlike computed properties, stored properties and deferred properties that require stored properties cannot be defined inextensionIn the.
  • Using delay attributes in a structure is usually not a good idea.
  • Be careful,lazyThe keyword does not perform any thread synchronization. If more than one thread attempts to access a delayed attribute before it completes the evaluation, the evaluation may be performed more than once, and the side effects of the evaluation may occur more than once.

The subscript

Custom subscript operation

  • We can add subscript support for our own types, or we can add new subscript overloading for existing types.

The subscript advanced

  • Subscripts are not limited to a single parameter. Subscripts can also use generics on parameters or return types. However, in most cases, it’s probably better to define a custom type for your data and then have those types satisfy the Codeable protocol for converting between values and data interchange formats.

The key path

  • A key path is an uncalled reference to a property, much like an unused reference to a method.
  • Key path expressions begin with a backslash, such as \string.count

Key paths that can be modeled by functions

  • One will be the base typeRootMapped to typeValueProperty, and a key path with(Root) -> ValueThe functions of type are very similar. For writable build paths, there is a pair of functions that get and set values. The biggest advantage of key paths over such functions, besides being more syntactically concise, is that they are values. You can test whether key paths are equal or use them as dictionary Spaces (since they comply with Hashable). Also, unlike functions, the key path does not contain state, so it does not capture mutable state. None of this is possible with ordinary functions.
  • However, key paths don’t give us the same degree of flexibility as functions. The key path dependence Value satisfies the Comparable premise.

Writable key path

  • Writable key paths are special: you can use them to read or write a value. Therefore, it is equivalent to a pair of functions: one to get the Value of the attribute ((Root) -> Value) and the other to set the Value of the attribute ((inout Root, Value) -> Void).

  • Writable key paths are especially useful for data binding

    extension NSObjectProtocol where Self: NSObject {
      func bind<A.Other> (_ keyPath: ReferenceWritableKeyPath<Self.A>, 
                         to other: Other._ otherKeyPath: ReferenceWritableKeyPath<Other.A>)- > (NSKeyValueObservation.NSKeyValueObservation)
      									where A: Equatable.Other: NSObject
      {
        let one = observe(keyPath, writeTo: other, otherKeyPath)
        let two = other.observe(otherKeyPath, writeTo: self, keyPath)
        return (one,two)
      }
    }
    
    final class Person: NSObkect {
      @objc dynamic var name: String = ""
    }
    
    class TextField: NSObject {
      @objc dynamic var text String = ""
    }
    
    let person = Person(a)let textField = TextField(a)let observation = person.bind(\.name, to: textField, \.text)
    person.name = "John"
    textField.text // John
    textField.text = "Sarah"
    person.name // Sarah
    Copy the code

Key path hierarchy

There are five different types of key paths, each with a more precise description and functionality on top of the previous:

  • AnyKeyPath and (Any) -> Any? Type functions are similar
  • PatialKeyPathand (Source) -> Any? The functions are similar.
  • KeyPathis similar to the (Source) -> Target function.
    ,>
  • WritableKeyPathand (Source) -> Target are similar to the (inout Source, Target) -> () pair.
    ,>
  • ReferenceWritableKeyPathand (Source) -> Target are similar to the (Source, Target) -> () pair. The second function updates the value of Source with Target and requires Source to be a reference type. It is essential to distinguish between WritableKeyPath and ReferenceWritableKeyPath, the setter for the former type requires its arguments to be inout.
    ,>

The hierarchy of these key paths is now implemented through class inheritance.

Compare objective-C key paths

  • In Foundation and Objective-C, key paths are modeled as strings

Future directions

  • One possible feature is throughCodableSerialize.

Automatic closure

  • && and | | operators short-circuit evaluation characteristics

  • To make our code more elegant, we can use the @Autoclosure annotation to tell the compiler that it should wrap a particular argument in a closure expression.

  • Automatic closures are also useful when implementing logging functions. For example, here is a log function that evaluates log messages only if the condition is true:

    func log(ifFalse condition: Bool.message: @autoclosure() - > (String),
            file: String = #file, function: String = #function, line: Int = #line)
    {
      guard !condition else { return }
      print("Assertion failed:\(message()).\(file): \(function) (line \(line))")}Copy the code

    This means that you can do expensive calculations in the passed expression without worrying about the overhead if the value is not used. The log function uses debugging identifiers like #file, #function, and #line. When used as the default arguments to a function, they can be useful when representing the filename of the caller, the function name, and the line number, respectively.

  • Use the automatic closure feature with caution.

    Overusing automatic closures can make your code difficult to understand. The context and function name when used should clearly indicate that the actual evaluation will be delayed.

@ escaping annotation

  • A closure that is stored somewhere (such as in a property) to be called later is calledEscape a closure. In contrast, a closure that never leaves the local scope of a function isNon-escape closure. For escape closures, the compiler forces us to use them explicitly in closure expressionsselfBecause inadvertently for self’sStrong referenceIs one of the most common causes of reference loops.
  • Note that the default non-escape rule applies only to function parameters and function types with immediate parameter positions. That is, if a stored property’s type is a function, it will escape (which is normal). Surprisingly, functions that use closures as arguments also escape if the closure is encapsulated in a type such as a meta-ancestor or an optional value. Because the closure is not a direct parameter in this case, it will automatically become an escape closure. As a result, you cannot write a function that takes parameters that satisfy both optional values and non-escape. In many cases, you can avoid optional values by providing a default value for closures. If this is not possible, you can get around this limitation by overloading the function and providing a function that contains optional values (escape) and a function that is not optional and does not escape.

withoutActuallyEscaping

  • We might be in a situation where we know for sure that a closure won’t escape, but the compiler can’t prove it, so it forces you to add it@escapingAnnotations. In this case, Swift provides onewithoutActuallyEscapingFunction as a kind of “safety exit”. This function allows you to pass in a non-escape closure to a function that accepts an escape closure.
  • Note that withoutActuallyEscaping, you enter the realm of insecurity in Swift. Allowing a copy of a closure to escape from the result of the withoutActuallyEscaping call results in uncertain behavior.