Six, closures
- Closures are self-contained blocks of function code that can be passed and used in code. Closures in Swift are similar to code blocks in C and Objective-C, and to anonymous functions in some other programming languages. Closures can capture and store references to arbitrary constants and variables in their context. It’s called wrapping constants and variables. Swift manages all memory operations involved in the capture process for you.
1. Closure expressions
- Closure expressions are a way to build inline closures, and their syntax is concise. Closure expressions provide several optimized syntactic abbreviations without losing the clarity of their syntax
{(parameters) -> return type in statements} reversedNames = names.sorted(by: Sorted (by: {$0 > $1})Copy the code
2. Trailing closures
- If a function takes a long closure expression as its last argument, the readability of the function can be enhanced by using a trailing closure, which is a closure expression written outside (after) the parentheses of the function call
func exec(v1: Int,v2: Int,fn:(Int,Int) -> Int) {
print(fn(v1,v2))
}
exec(v1: 10, v2: 20) {
$0 + $1
}
Copy the code
- If the closure expression is the only argument to the function and the trailing closure syntax is used, there is no need to write parentheses after the function name
func exec(fn: (Int, Int) -> Int) {
print(fn(1, 2))
}
exec(fn: { $0 + $1 })
exec() { $0 + $1 }
exec { $0 + $1 }
Copy the code
Value capture
- Closures can capture constants or variables in the context in which they are defined. Even if the original scope that defined these constants and variables no longer exists, closures can still reference and modify these values inside the closure function.
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return //makeIncrementer returns type () -> IntCopy the code
Closures are reference types
5. Escape closure
- When a closure is passed to a function as an argument, but the closure is not executed until the function returns, the closure is said to escape from the function. When you define a function that takes a closure as an argument, you can use the tag @escaping before the argument name to indicate that the closure is allowed to “escape” from the function.
var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } } let instance = SomeClass() instance.doSomething() Print (instance.x) // Print "200" completionHandlers. First? () print(instance.x)Copy the code
6. Automatic closure
-
@autoClosure will automatically wrap 20 into a closure {20}
-
@Autoclosure only supports arguments in () -> T format
-
@Autoclosure does not support only the last parameter
-
Null merge operator?? The @Autoclosure technique is used
-
@autoclosure and no @Autoclosure constitute function overloading
func getFirstPositive(_ v1: Int,_ v2: Int)->Int { return v1>0 ? v1 : v2 } func getFirstPositive(_ v1: Int,_ v2: @autoclosure ()-> Int)->Int { return v1>0 ? v1 : v2() } print(getFirstPositive(-1, 20)) Copy the code
Seven, the enumeration
1. Enumeration syntax
- Use the enum keyword to create enumerations and place their entire definition in a pair of braces
2. Use the Switch statement to match the enumerated value
3. Traversal of enumerators
-
Make the enumeration follow the CaseIterable protocol. Swift generates an allCases attribute that represents a collection containing all members of the enumeration
enum Beverage: CaseIterable { case coffee, tea, Juice} let numberOfChoices = Beverage. AllCases. Count print("\(numberOfChoices) beverages available") // Print "3 beverages AllCases {print(beverage)} // coffee // tea // juiceCopy the code
4. Associated values
-
Sometimes it is useful to store values of other types along with member values. This additional information is called the correlation value
enum Barcode { case upc(Int, Int, Int, Int) case qrCode(String) } Copy the code
5. Original value
-
The original value can be a string, a character, or any integer or floating point value. Each raw value must be unique in the enumeration declaration
enum ASCIIControlCharacter: Character { case tab = "\t" case lineFeed = "\n" case carriageReturn = "\r" } Copy the code
-
When using an enumeration whose original value is an integer or a string, you do not need to explicitly set the original value for each enumerator; Swift will automatically assign the value to you.
-
If the first enumerator does not set its original value, its original value will be 0
let positionToFind = 11 if let somePlanet = Planet(rawValue: positionToFind) { switch somePlanet { case .earth: print("Mostly harmless") default: print("Not a safe place for humans") } } else { print("There isn't a planet at position \(positionToFind)") } // Print "There isn't a planet at Position 11"Copy the code
6. Recursive enumeration
-
A recursive enumeration is an enumeration type that has one or more enumerators that use instances of that enumeration type as associated values. When using recursive enumerations, the compiler inserts a layer of indirection. You can indicate that the enumeration member is recursive by adding indirect before it.
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
Copy the code
Classes and structures
1. Structure and class comparison
A. Similarities between the two:
- Define attributes to store values
- Define methods to provide functionality
- Defines subscript operations to access their values through subscript syntax
- Define the constructor used to set initial values
- Extend to add functionality beyond the default implementation
- Follow protocols to provide some standard functionality
B. Differences:
Class:
- Inheritance allows a class to inherit the characteristics of another class
- Type conversions allow the type of an instance of a class to be checked and interpreted at run time
- A destructor allows an instance of a class to free any resources it has allocated
- Reference counting allows multiple references to a class
C. The syntax of the definition
Struct SomeStructure {// define struct SomeClass {// define class SomeClass {// define class SomeClass}Copy the code
Structs and enumerations are value types
-
When a value type is assigned to a var, let, or function, it copies everything directly
-
This operation is similar to copying or paste a file to create a new copy of the file. Belong to deep copy
Classes are reference types
-
A reference assignment to var. let or to a function is a copy of the memory address
-
Similar to making a double of a file (shortcut, link) that points to the same file. Belongs to a shallow copy
Nine, attributes,
1. Storage properties
A. the grammar:
A stored property is a constant or variable stored in an instance of a particular class or structure. A store property can be a variable store property (defined with the keyword var) or a constant store property (defined with the keyword let).
- Similar to the concept of a member variable
- Stored in the instance’s memory
- Structures and classes can define storage properties
- Enumerations cannot define storage properties
struct FixedLengthRange { var firstValue: Int let length: Int } var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) / / said between the integer 0 rangeOfThreeItems. FirstValue = 6 / / the area between said integer June nowCopy the code
B. Delay storage properties
- Using lazy, you can define a lazy storage property that is initialized only when the property is first used
- The lazy attribute must be a var, not a let
- The let must have the value before the instance’s initialization method completes
- If multiple threads access the lazy property for the first time
- There is no guarantee that the property will be initialized only once
- When a structure contains a deferred storage property, only var can access the deferred storage property
- This is because delayed initialization requires a change in the structure’s memory
2. Calculate properties
A. the grammar
Instead of storing values directly, computed properties provide a getter and an optional setter to indirectly obtain and set the values of other properties or variables.
- The essence is the method (function)
- Does not occupy the memory of the instance
- Enumerations, structures, and classes can all define computed properties
B. Pay attention to the point
- If the setter for the evaluated property does not define a parameter name that represents the newValue, the default name newValue can be used
C. Read-only computing attributes
-
A computed property that has only a getter and no setter is called a read-only computed property. A read-only calculation property always returns a value that can be accessed through the dot operator, but cannot set a new value.
-
The var keyword must be used to define computed properties, including read-only computed properties, because their values are not fixed. The let keyword is used only to declare constant properties, representing values that cannot be changed after initialization.
-
Read-only computed properties can be declared without the GET keyword and curly braces
Struct Cuboid {var width = 0.0, height = 0.0, depth = 0.0 var volume: struct Cuboid {var width = 0.0, height = 0.0, depth = 0.0 Double {return width * height * depth}} let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 5.0) 2.0) print(" The volume of fourByFiveByTwo is \(fourbyfivebytwo-volume)") // Print "The volume of fourByFiveByTwo is 40.0"Copy the code
3. Attribute viewer
A. Where to add attribute viewer:
- Custom storage properties
- Inherited storage properties
- Inherited computed properties
B. Points to note:
-
You can set property observers for non-lazy VAR storage properties
-
WillSet will pass a newValue, which is called newValue by default
-
DidSet will pass the oldValue, oldValue by default
-
Setting property values in an initializer does not trigger willSet and didSet
-
Setting an initial value at property definition also does not trigger willSet and didSet
class StepCounter { var totalSteps: Int = 0 {willSet(newTotalSteps) {print(" set totalSteps to \(newTotalSteps)")} didSet {if totalSteps > oldValue { Print (" add \(totalSteps -oldValue) step ")}}}} let stepCounter = stepCounter () stepCounter. TotalSteps = 200 TotalSteps = 360 // totalSteps = 360 // add 160 steps StepCounter. TotalSteps = 896 // Set totalSteps to 896 // add 536 stepsCopy the code
Attribute wrappers
A. definition
The property wrapper adds a layer of separation between the code that manages how properties are stored and defines them.
@propertyWrapper
struct TwelveOrLess {
private var number: Int
init() { self.number = 0 }
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
Copy the code
5. Global and local variables
Global variable: A variable defined outside of a function, method, closure, or any type
Local variables: Variables defined inside a function, method, or closure
6. Type attributes
A. Instance properties
- It can only be accessed by instance
- Stored Instance Property: Stored in the memory of the Instance. Each Instance has one copy
- Computed Instance Property
B. Instance attributes
- Type Property: Can only be accessed by Type
- Stored Type Property: During the entire run of the program, only one portion of memory is Stored (similar to global variables).
- Computed Type Properties (Computed Type Property)
- You can define type attributes through static
- If it is a class, the keyword class can also be used
C. Type attribute details
-
Unlike store instance properties, you must set initial values for store type properties
-
Because types don’t have an init initializer like instances do to initialize storage properties
-
The storage type attribute is lazy by default and is initialized the first time it is used
-
It is guaranteed to be initialized only once, even if accessed by multiple threads at the same time
-
The storage type attribute can be let
-
Enumerated types can also define type properties (storage type properties, computed type properties)
struct SomeStructure { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { return 1 } } enum SomeEnumeration { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { return 6 } } class SomeClass { static var storedTypeProperty = "Some value." static var computedTypeProperty: Int { return 27 } class var overrideableComputedTypeProperty: Int 107} {return} print (SomeStructure storedTypeProperty) / / print "value. Some" SomeStructure storedTypeProperty = "Another value." print (SomeStructure storedTypeProperty) / / print "Another value." PutedTypeProperty print (SomeEnumeration.com) / / print "6" print putedTypeProperty (SomeClass.com) / / print "27"Copy the code