Swift5.5学习笔记六:闭包(Closures)

//闭包(Closures)
//闭包是独立的功能块,可以在代码中传递和使用。
//Swift中的闭包类似于 C 和 Objective-C 中的Block以及其他编程语言中的 lambda

//一、闭包表达式
//闭包表达式是一种以简短、重点突出的语法编写内联闭包的方法。
//闭包表达式提供了多种语法优化为以缩短的形式编写闭包,而不会失去清晰度或意图。

//1.排序方法
//Swift的标准库提供了一个名为sorted(by:)的方法,该方法根据您提供闭包的输出的排序对已知类型的数组值进行排序。
//一旦完成排序过程,该sorted(by:)方法将返回一个与旧数组具有相同类型和大小的新数组,其元素按正确的排序顺序排列。原始数组不会被该sorted(by:)方法修改。
//下面的闭包表达式示例使用该sorted(by:)方法按逆字母顺序对一组String值进行排序。这是要排序的初始数组:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

//该sorted(by:)方法接受一个闭包,该闭包采用与数组内容相同类型的两个参数,并根据第一个值应该出现在第二个值之前还是之后来返回一个Bool值说明值如何进行排序
//如果第一个值应该出现在第二个值之前,则排序闭包需要返回true,否则返回false。

//以下这个例子是对一个String值数组进行排序,排序闭包需要是一个类型为(String, String) -> Bool的函数

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

//如果第一个字符串(s1)大于第二个字符串(s2),函数backward(::)将返回true,表示排序数组中s1应该出现在s2之前
//对于字符串中的字符,“大于”表示“在字母表中出现的晚”。这意味着字母"B"“大于”字母"A",并且字符串"Tom"大于字符串"Tim"。
//这给出了反向字母排序,"Barry"放置在"Alex"之前,依此类推。

//2.闭包表达式语法
//闭包表达式语法具有以下一般形式:

{ (parameters) -> return type in
    statements
}

//该参数在封闭表达式语法可以是输入输出参数,但是他们不能有一个默认值。
//如果您命名的是可变参数,则可以使用可变参数。元组也可以用作参数类型和返回类型。

//下面的示例显示了上述backward(::)函数的闭包表达式版本:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]

//请注意,此内联闭包的参数和返回类型的声明与backward(::)函数的声明相同。
//在这两种情况下,它都写为(s1: String, s2: String) -> Bool
//但是,对于内联闭包表达式,参数和返回类型写在花括号内,而不是在花括号外。
//闭包主体的开始由in关键字开始。这个关键字表示闭包的参数和返回类型的定义已经完成,闭包的主体即将开始
//因为闭包的主体很短,它甚至可以写成一行:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]

//3.从上下文推断类型
//因为排序闭包作为参数传递给方法,Swift可以推断其参数的类型和它返回的值的类型。
//该sorted(by:)方法是在字符串数组上调用的,因此其参数必须是类型为(String, String) -> Bool的函数。
//这意味着(String, String)和Bool类型不需要写成闭包表达式定义的一部分。因为可以推断所有类型,所以也可以省略返回箭头(->)和参数名称周围的括号

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]

//4.单表达式闭包的隐式返回
//单表达式闭包可以省略关键字return,通过从声明中隐式返回其单个表达式的结果
//如上例的版本可以修改为:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]

//5.简写参数名称
//Swift自动为内联闭包提供参数名称简写,可通过参数1、$2推断简写参数名称的值
//如果您在闭包表达式中使用这些简写参数名称,则可以从其定义中省略闭包的参数列表。
//简写参数名称的类型是从预期的函数类型推断出来的,您使用的编号最高的简写参数名称决定了闭包采用的参数数量。
//关键字in也可以被省略,因为闭包表达式完全是有闭包创建的

reversedNames = names.sorted(by: { $0 > $1 } )
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]

//在这里,1引用闭包的第一个和第二个字符串参数。
//因为1是编号最高的简写参数,所以闭包被理解为带有两个参数。 //因为这里的函数sorted(by:)需要一个闭包,它的参数都是字符串,简写参数0和$1都是String类型

//6.运算符方法
//实际上有一种更短的方法来编写上面的闭包表达式。
//Swift的String类型将大于运算符(>)的字符串特定实现定义为一个方法,该方法具有两个String类型参数,并返回一个Bool类型值。
//这与sorted(by:)方法所需的方法类型完全匹配。因此,您可以简单地传入大于运算符(>),Swift将推断您要使用其字符串特定的实现:

reversedNames = names.sorted(by: >)
print(reversedNames)
//["Ewa", "Daniella", "Chris", "Barry", "Alex"]

//二、尾随闭包
//如果您需要将闭包表达式作为函数的最终参数传递给函数,并且闭包表达式很长,那么将其写为尾随闭包会很有用。
//你在函数调用的括号之后写了一个尾随闭包,即使尾随闭包仍然是函数的一个参数。
//当您使用尾随闭包语法时,您不会将第一个闭包的参数标签作为函数调用的一部分。
//一个函数调用可以包含多个尾随闭包;但是,下面的前几个示例使用单个尾随闭包。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // function body goes here
    print("someFunctionThatTakesAClosure")
}

// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
    // closure's body goes here
})

// Here's how you call this function with a trailing closure instead:

someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

//上面闭包表达式语法部分的字符串排序闭包可以写在sorted(by:)方法的括号之外作为尾随闭包:

reversedNames = names.sorted() { $0 > $1 }
print(reversedNames)

//如果闭包表达式作为函数或方法的唯一参数提供,并且您将该表达式作为尾随闭包提供
//当在调用函数时,则无需在函数或方法名称后写一对括号():

reversedNames = names.sorted { $0 > $1 }

//当闭包足够长以至于无法将其内联写入一行时,尾随闭包最有用。
//例如,Swift的Array类型有一个map(:)方法,它接受一个闭包表达式作为它的单个参数,
//为数组中的每个项目调用一次闭包,并为该项目返回一个替代的映射值(可能是其他类型的)
//您可以通过map(
:)方法,在闭包中编写代码来指定映射的性质和返回值的类型

//以下是使用map(_:)方法将带有尾随闭包的Int值数组转换为String值数组。
//该数组用于创建新数组:[16, 58, 510] ["OneSix", "FiveEight", "FiveOneZero"]

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

//您现在可以使用numbers数组来创建String值数组,方法是将闭包表达式map(_:)作为尾随闭包传递给数组的方法:

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}

print(strings)
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

//该map(_:)方法为数组中的每个项目调用一次闭包表达式。
//您不需要指定闭包输入参数number的类型,因为可以从要映射的数组中的值推断出类型。

//三、捕捉值
//在Swift 中,可以捕获值的最简单的闭包形式是嵌套函数,它写在另一个函数的主体中。
//嵌套函数可以捕获其外部函数的任何参数,也可以捕获外部函数中定义的任何常量和变量。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

//下面是一个调用makeIncrementer的例子:
let incrementByTen = makeIncrementer(forIncrement: 10)
//这个例子设置了一个被调用的常量incrementByTen来引用一个增量函数,每次调用runningTotal时它都会添加10到它的变量中。
//多次调用该函数显示了这种行为:

incrementByTen()
// returns a value of 10
incrementByTen()
// returns a value of 20
incrementByTen()
// returns a value of 30

//如果您创建第二个增量器,它将拥有自己存储的对新的单独runningTotal变量的引用:

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// returns a value of 7

//incrementByTen再次调用原来的incrementer()会继续增加它自己的runningTotal变量,并且不会影响被incrementBySeven捕获的变量:

incrementByTen()
// returns a value of 40

//备注:如果您将闭包分配给类实例的属性,并且该闭包通过引用该实例或其成员来捕获该实例,那么您将在该闭包和该实例之间创建一个强引用循环。
//Swift使用捕获列表来打破这些强引用循环

//四、闭包是引用类型
//在上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量所指的闭包仍然能够捕获变量runningTotal增加。这是因为函数和闭包是引用类型。
//每当您将函数或闭包分配给常量或变量时,您实际上是在将该常量或变量设置为对该函数或闭包的引用。
//这也意味着,如果您将一个闭包分配给两个不同的常量或变量,那么这两个常量或变量都指向同一个闭包。

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50
incrementByTen()
// returns a value of 60

//上面的例子表明调用alsoIncrementByTen与调用incrementByTen相同。
//因为它们都引用同一个闭包,所以它们都递增并返回相同的运行总数。

//五、转义闭包
//当闭包作为参数传递给函数时,闭包被称 为对函数进行转义,在函数返回后被调用。
//当您声明一个函数将闭包作为其的参数之一时,您可以在参数类型之前写入”@escaping“以说明允许闭包转义。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

//该someFunctionWithEscapingClosure(_:)函数将一个闭包作为其参数并将其添加到在函数外部声明的数组中。
//如果你没有用@escaping标记这个函数的参数,会发生编译时错误。
//如果self引用了一个引用self自己的的转义闭包,需要特别考虑一下self。在转义闭包中捕获self很容易意外地创建一个强引用循环

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)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

//这是通过self包含在闭包的捕获列表中来捕获的self的doSomething()版本,这里self隐式引用

class SomeOtherClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { [self] in x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

//如果self是结构或枚举,则始终可以隐式引用self
//但是,当self是结构或枚举的情况,可变引用转义闭包无法捕获self

struct SomeStruct {
    var x = 10
    mutating func doSomething() {
        someFunctionWithNonescapingClosure { x = 200 }  // Ok
//        someFunctionWithEscapingClosure { x = 100 }     // Error
    }
}

//对上面示例中someFunctionWithEscapingClosure的函数的调用会报错误,因为self在一个可变方法中,所以是可变的。这违反了在结构中,转义闭包不能捕获可变引用self的规则

//六、自动闭包
//一个自动闭包是自动创建一个表达式作为参数传递给函数的闭包。当它被调用时,不用带任何参数,它返回被包含在里面的的表达式的值。
//通过编写一个普通表达式而不是显示的闭包,这种语法方便让你忽略函数参数前后的大括号
//自动闭包调用函数非常常见,但是通常不实现这种函数
//比如:assert(condition:message:file:line:) 方法给它的条件和消息参数使用一个自动闭包
//它的条件参数仅在debug构建的时候被使用,并且仅在它的条件参数为false的时候被使用
//自动闭包让你延迟执行代码,因为内部代码直到你调用自动闭包的时候才会执行
//延迟执行对于有副作用或者计算量大的代码非常有用,因为自动闭包让你可以控制什么时候执行代码
//下面的代码示例了自动闭包如何执行

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// Prints "5"

let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// Prints "5"

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// Prints "4"

//上面的代码中,即使customersInLine数组的第一个元素在自动闭包代码中被删除,数组元素直到自动闭包真实执行才被删除
//假如闭包一直不调用,闭包里面的表达式将永远不会被执行,这意味着数组元素将不会被删除
//这里,customerProvider的类型不是字符串,而是一个午餐返回值为String的() -> String函数

//当你传递一个闭包给一个函数作为参数,延迟执行将同样有效

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"

//上面的代码中,serve(customer:)函数使用显示闭包返回一个元素的值。
//在下面的serve(customer:)函数示例中,将不使用显示闭包,而是通过在函数参数类型上加一个@autoclosure属性,使用自动闭包实现同样的效果
//现在你可以调用向函数传递String参数而不是闭包一样调用函数
//因为customerProvider的参数类型标记了@autoclosure属性,参数会自动传递给闭包

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

print(customersInLine)
//Prints "["Barry", "Daniella"]"

//备注:过度使用自动闭包将是你的代码难以理解,上下文和函数名称应该清楚地表明正在延迟执行

//如果你希望自动闭包允许被转义,你可以同时使用@autoclosure和@escaping属性

// customersInLine is ["Barry", "Daniella"]
var customerProviders: [() -> String] = []

func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}

collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."

for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"


print(customersInLine)
//Prints "[]"

//在上面的代码中,collectCustomerProviders(_:)添加闭包到customerProviders数组,而不是调用闭包作为参数传递给customerProvider
//数组被定义在函数作用域的外面,这意味着数组中的闭包可以在函数返回后执行
//因此,customerProvider必须允许参数脱离函数的作用域

你可能感兴趣的:(Swift5.5学习笔记六:闭包(Closures))