Swift5.x-闭包(中文文档)

引言

继续学习Swift文档,从上一章节:函数,我们学习了Swift函数相关的内容,如函数的定义和使用、函数参数、返回值、嵌套函数等这些内容。在定义和用法上,与OC还是有差异的。现在,我们学习Swift的闭包相关的内容。由于篇幅较长,这里分篇来记录,接下来,Fighting!

如果你已经掌握了闭包的使用,那么请参阅下一章节:枚举

闭包

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

闭包可以从定义它们的上下文中捕获和存储对任何常量和变量的引用。 这称为包装这些常量和变量。 Swift为您处理捕获的所有常量或变量进行内存管理。

注意
如果您不熟悉捕获的概念,请不要担心。 下面在Capturing Values中对此进行了详细说明。

正如函数中介绍的那样,全局和嵌套函数实际上是闭包的特殊情况。 闭包采用以下三种形式之一:

  • 全局函数是具有名称且不捕获任何值的闭包。
  • 嵌套函数是具有名称的闭包,可以从其闭包函数捕获值。
  • 闭包表达式是用轻量级语法编写的未命名的闭包,可以从其周围的上下文中捕获值。

Swift的闭包表达式具有简洁明了的风格,并通过优化来鼓励在常见情况下使用简洁,简洁的语法。 这些优化包括:

  • 从上下文推断参数和返回值类型
  • 单表达式闭包的隐式返回
  • 速记参数名称
  • 尾随闭包语法

1 闭包表达式

嵌套函数中介绍的Nested Functions是命名和定义独立的代码块作为较大函数的一部分的便捷方法。 但是,有时在没有完整的声明和名称的情况下编写类似函数的结构的较短版本很有用。 当您使用以函数作为其一个或多个参数的函数或方法时,尤其如此。

闭包表达式是一种以简短,集中的语法编写内联闭包的方法。 闭包表达式提供了几种语法优化,以简化形式编写闭包,而不会造成清晰度或意图的损失。 下面的闭包表达式示例通过在多个迭代中完善sorted(by :)方法的单个示例来说明这些优化,每个迭代以更简洁的方式表示相同的功能。

1.1 Sorted方法

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的函数。

提供排序闭包的一种方法是编写正确类型的普通函数,并将其作为参数传递给sorted(by :)方法:

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”之前,依此类推。

但是,这实际上是编写单表达式函数(a> b)的漫长过程。 在此示例中,最好使用闭包表达式语法内联地编写排序闭包。

1.2 闭包表达式语法

闭包表达式语法基本格式:

{ (parameters) -> return type in
    statements
}

闭包表达式语法中的参数可以是in-out参数,但不能具有默认值。 如果您命名可变参数,则可以使用可变参数。 元组也可用作参数类型和返回类型。

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

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

请注意,此内联闭包的参数声明和返回类型与backward(_ : _ :)函数的声明相同。 在两种情况下,它都写为(s1:String,s2:String)-> Bool。 但是,对于内联闭包表达式,参数和返回类型写在花括号内,而不是花括号外。

闭包body的开头由in关键字修饰。 此关键字表示闭包的参数和返回类型的定义已经完成,并且闭包的body即将开始。

因为闭包的body非常短,所以它甚至可以写在一行上:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

这说明对sorted(by :)方法的总体调用保持不变。 一对括号仍然包裹了该方法的整个参数。 但是,该参数现在是内联闭包。

1.3 从上下文推断类型

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

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

当将闭包作为内联闭包表达式传递给函数或方法时,总是可以推断出参数类型和返回类型。 因此,当闭包用作函数或方法参数时,您无需编写其完整形式的内联闭包。

尽管如此,您仍然可以根据需要使类型显式,并且如果这样做可以避免代码阅读者产生歧义,则鼓励这样做。 在使用sorted(by :)方法的情况下,闭包的目的很明显,即发生了排序,并且读者可以放心地认为闭包很可能与String值一起使用,因为 它有助于对字符串数组进行排序。

1.4 单表达式闭包的隐式返回

单表达式闭包可以通过从声明中省略return关键字来隐式返回其单表达式的结果,如上一个示例的此版本所示:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

在这里,sorted(by :)方法的参数的函数类型清楚地表明,闭包必须返回Bool值。 由于闭包的主体包含一个返回Bool值的表达式(s1> s2),因此没有歧义,可以省略return关键字。

1.5 速记参数名称

Swift会自动为内联闭包提供速记参数名称,可使用0,1,$2等名称来引用闭包参数的值。

如果您在闭包表达式中使用这些速记参数名称,则可以从其定义中省略闭包的参数列表,而速记参数名称的数量和类型将从预期的函数类型中推断出来。 也可以省略in关键字,因为闭包表达式完全由其主体组成:

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

在这里,0和1指的是闭包的第一个和第二个String参数。

1.6 操作符方法

实际上,还有一种更短的方法来编写上述闭包表达式。 Swift的String类型将大于运算符(>)的特定于字符串的实现定义为一种方法,该方法具有两个String类型的参数,并返回Bool类型的值。 这与sorted(by :)方法所需的方法类型完全匹配。 因此,您只需传递大于号运算符,Swift就会推断出您想使用其特定于字符串的实现:

reversedNames = names.sorted(by: >)

有关更多的操作符方法,参见Operator Methods

2 尾随闭包

如果您需要将闭包表达式作为函数的最终参数传递给函数,并且闭包表达式很长,那么将其写为尾随闭包可能会很有用。 即使尾随闭合符仍然是函数的参数,您也可以在函数调用的括号后面写一个尾随闭合符。 使用尾随闭包语法时,不要在函数调用的过程中为第一个闭包编写参数标签。 一个函数调用可以包括多个尾随的闭包。 但是,下面的前几个示例使用单个尾随闭包。

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

// 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 }

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

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

当闭包足够长以致无法在一行中内联写入时,尾随闭包最有用。 例如,Swift的Array类型具有map(_ :)方法,该方法将闭包表达式作为其单个参数。 对数组中的每个项目调用一次闭包,并为该项目返回一个替代的映射值(可能是其他类型的)。 通过在传递给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]

上面的代码创建了一个整数数字和其名称的英语版本之间的映射字典。 它还定义了一个整数数组,可以将其转换为字符串。

现在,您可以通过将闭包表达式作为尾随闭包传递到数组的map(_ :)方法中,来使用数字数组创建String值数组:

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
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

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

在此示例中,变量数字用闭包的number参数的值初始化,以便可以在闭包主体中修改该值。 (函数和闭包的参数始终是常量。)闭包表达式还指定String的返回类型,以指示将存储在映射的输出数组中的类型。

闭包表达式每次被调用时都会构建一个名为output的字符串。 它使用余数运算符(number%10)计算数字的最后一位,并使用此数字在digitNames词典中查找适当的字符串。 闭包可用于创建任何大于零的整数的字符串表示形式。

注意
调用digitNames字典的下标后会带有感叹号(!),因为字典下标会返回一个可选值,以指示如果键不存在,则字典查找可能会失败。 在上面的示例中,可以确保number%10始终是digitNames词典的有效下标键,因此使用感叹号可以强制解开存储在下标的可选返回值中的String值。

从digitNames词典中检索到的字符串将添加到output的前面,从而有效地反向构建了数字的字符串版本。 (表达式数字%10为16表示6的值,对于58表示8的值,对于510表示0。)

然后将数字变量除以10。由于它是整数,因此在除法过程中会四舍五入,因此16变为1,58变为5,而510变为51。

重复该过程,直到number等于0,这时闭包返回output字符串,并通过map(_ :)方法将其添加到输出数组中。

在上面的示例中,尾随闭包语法的使用在闭包支持的函数之后立即将闭包的功能巧妙地封装起来,而无需将整个闭包包装在map(_ :)方法的外部括号内。

如果一个函数使用多个闭包,则可以省略第一个尾随闭包的参数标签,并标记其余的尾随闭包。 例如,下面的函数为照片库加载图片:

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
    if let picture = download("photo.jpg", from: server) {
        completion(picture)
    } else {
        onFailure()
    }
}

调用此函数加载图片时,将提供两个闭包。 第一个关闭是完成处理程序,该处理程序在成功下载后显示图片。 第二个闭包是一个错误处理程序,向用户显示错误。

loadPicture(from: someServer) { picture in
    someView.currentPicture = picture
} onFailure: {
    print("Couldn't download the next picture.")
}

在此示例中,loadPicture(from:completion:onFailure :)函数将其网络任务分派到后台,并在网络任务完成时调用两个完成处理程序之一。 通过这种方式编写函数,可以让您轻松地将负责处理网络故障的代码与成功下载后更新用户界面的代码区分开来,而不必使用只处理两种情况的闭包。

3 捕获值

闭包可以从定义它的周围环境中捕获常量和变量。 然后,闭包可以从其主体内部引用和修改那些常量和变量的值,即使定义常量和变量的原始范围不再存在。

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

这是一个名为makeIncrementer的函数的示例,其中包含一个嵌套的函数,称为递增器。 嵌套的crementer()函数从其周围的上下文中捕获两个值,runningTotal和amount。 捕获这些值后,makeIncrementer将递增器作为闭合函数返回,该闭合器将在每次调用时将runningTotal增加一个量。

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

makeIncrementer的返回类型为()-> Int。 这意味着它将返回一个函数,而不是一个简单的值。 它返回的函数没有参数,并且每次调用都返回一个Int值。 要了解函数如何返回其他函数,请参见Function Types as Return Types

makeIncrementer(forIncrement :)函数定义了一个名为runningTotal的整数变量,用于存储将要返回的增量器的当前运行总计。 该变量的初始值为0。

makeIncrementer(forIncrement :)函数具有单个Int参数,其参数标签为forIncrement,参数名称为amount。 传递给此参数的参数值指定每次调用返回的增量器函数时,应该将runningTotal增加多少。 makeIncrementer函数定义了一个称为增量器的嵌套函数,该函数执行实际的增量。 此函数只是将金额添加到runningTotal,然后返回结果。

当单独考虑时,嵌套的增量器()函数可能看起来很不寻常:

func incrementer() -> Int {
    runningTotal += amount
    return runningTotal
}

incrementer()函数没有任何参数,但它是从函数体内引用runningTotal和amount的。 它通过捕获周围函数对runningTotal和amount的引用并在其自己的函数体内使用它们来实现此目的。 通过引用捕获可以确保在对makeIncrementer的调用结束时runningTotal和数量不会消失,并且还可以确保下次调用incrementer函数时runningTotal可用。

注意
作为一种优化,Swift可能会捕获并存储值的副本,如果该值未被闭包更改,并且在创建闭包后也未更改该值。

当不再需要变量处理时,Swift还可以处理所有与变量处理有关的内存管理。

下例是makeIncrementer的实现:

let incrementByTen = makeIncrementer(forIncrement: 10)

本示例设置一个称为crementByTen的常量,以引用一个增量器函数,该函数在每次调用它的runningTotal变量时将其加10。 多次调用该函数可显示此行为:

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

如果创建第二个incrementer,它将有其自己的存储的对新的单独的runningTotal变量的引用:

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

再次调用原始的增量器(incrementByTen)会继续增加其自己的runningTotal变量,并且不会影响incrementBySeven捕获的变量:

incrementByTen()
// returns a value of 40

注意
如果将闭包分配给类实例的属性,并且闭包通过引用该实例或其成员来捕获该实例,则将在闭包和实例之间创建一个循环引用。 Swift使用捕获列表来打破这些循环引用。 有关更多信息,请参见Strong Reference Cycles for Closures

4 闭包是引用类型

在上面的示例中,increasingBySeven和increasingByTen是常量,但是这些常量引用的闭包仍然能够增加它们已捕获的runningTotal变量。 这是因为函数和闭包是引用类型。

每当您将函数或闭包分配给常量或变量时,实际上就是在将该常量或变量设置为对该函数或闭包的引用。 在上面的示例中,incrementByTen引用的闭包是常量,而不是闭包本身的内容。

这也意味着,如果将闭包分配给两个不同的常量或变量,则这两个常量或变量都引用同一闭包。

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

incrementByTen()
// returns a value of 60

上面的示例显示,调用AlsoIncrementByTen与调用crementByTen相同。 因为它们都引用相同的闭包,所以它们都递增并返回相同的运行总计。

5 Escaping 闭包

当闭包作为函数的参数传递给闭包时,闭包被认为是对函数的转义,但是在函数返回后会被调用。 声明将闭包作为其参数之一的函数时,可以在参数类型之前写@escaping,以指示允许转义闭包。

闭包可以转义的一种方法是将其存储在函数外部定义的变量中。 例如,许多启动异步操作的函数都将闭包作为参数用作完成处理程序。 该函数在开始操作后返回,但是直到操作完成后才调用闭包,闭包需要转义,稍后再调用。 例如:

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

someFunctionWithEscapingClosure(_ :)函数将闭包作为参数,并将其添加到在函数外部声明的数组中。 如果不使用@ecaping标记此函数的参数,则会出现编译时错误。如果self引用类的实例,则引用self的转义闭包需要特别考虑。 用转义的闭包捕获self,很容易意外地创建一个循环引用。 有关循环引用的信息,请参见自动引用计数

通常,闭包通过在闭包主体中使用变量来隐式捕获变量,但是在这种情况下,您需要明确表示。 如果您要捕获self,请在使用时明确写出self,或将self包括在闭包的捕获列表中。 明确地写出self,可以表达self的意图,并提醒您确认没有循环引用。 例如,在下面的代码中,传递给someFunctionWithEscapingClosure(_ :)的闭包显式地引用了self。 相比之下,传递给someFunctionWithNonescapingClosure(_ :)的闭包是不转义的闭包,这意味着它可以隐式引用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"

这是doSomething()的一个版本,它通过将self包含在闭包的捕获列表中来捕获self,然后隐式地引用self:

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

注意
someFunctionWithEscapingClosure { [self] in x = 100 }这段代码,在实践时,Xcode会报错,需要显示的引用self,Xcode提示在x前面加上self.

如果self是结构体或枚举的实例,则始终可以隐式引用self。 但是,当self是结构体或枚举的实例时,转义的闭包无法捕获对self的可变引用。 结构体和枚举不允许共享可变性,如结构体和枚举是值类型中所述。

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

上例中对someFunctionWithEscapingClosure函数的调用是错误的,因为它位于mutating方法内部,因此self是可变的。 对于结构体来说,这违反了转义闭包不能捕获对self的可变引用的规则。

6 自动闭包

自动闭包是一种自动创建的闭包,用于包装作为参数传递给函数的表达式。 它不带任何参数,调用时将返回包装在其中的表达式的值。 这种语法上的便利性使您可以通过编写正则表达式而不是显式闭包来省略函数参数的花括号。

调用具有自动闭包功能的函数很常见,但是实现这种功能并不常见。 例如,assert(condition:message:file:line :)函数会自动闭包其条件和消息参数; 仅在调试版本中评估其条件参数,并且仅在condition为false时评估其message参数。

自动闭包功能可让您延迟评估,因为在调用闭包功能之前,内部代码不会运行。 延迟评估对于具有副作用或计算量大的代码很有用,因为它使您可以控制何时评估该代码。 下面的代码显示了闭包如何延迟评估。

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"

即使customerInInLine数组的第一个元素已由闭包中的代码删除,但只有在实际调用闭包之前,才删除array元素。 如果从不调用闭包,则闭包内部的表达式不会被求值,这意味着数组元素不会被删除。 请注意,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!"

注意
过度使用自动闭包功能会使您的代码难以理解。 上下文和函数名称应明确表明评估被推迟。

如果要允许自动闭包可以转义,请同时使用@autoclosure和@escaping属性。 @escaping属性在Escaping Closures中进行了描述。

// 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!"

在上面的代码中,collectCustomerProviders(_ :)函数将闭包附加到customerProviders数组上,而不是调用作为其customerProvider参数传递给它的闭包。 数组在函数范围之外声明,这意味着可以在函数返回后执行数组中的闭包。 因此,必须允许customerProvider参数的值转义该函数的范围。

总结

这一章节内容刚开始学习的时候有点懵逼,Swift里的闭包类似于OC里面的block,与OC里的block有点差异,下面来总结一下:

  • 闭包是独立的功能块,可以在代码中传递和使用,类似于C和Objective-C中的块。
  • 闭包表达式语法基本格式:
{ (parameters) -> return type in
    statements
}

如将闭包作为sorted函数的参数:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

由于body非常短,可以写在一行上:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
  • 闭包可以从上下文来推断类型,所以上面的表达式还可以简写为:
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
  • 闭包的body里面是一个单表达式,则可以隐式返回:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

  • Swift里提供了速记参数名称来简化闭包:
reversedNames = names.sorted(by: { $0 > $1 } )
  • 操作符方法更加的简化了闭包:
reversedNames = names.sorted(by: >)

注意
不是所有的闭包都可以如上面的方式简写,要根据调用函数的功能(如sorted函数等)和变量的类型(如字符串、整型等)来判断是否可以简写。

  • 尾随闭包:将闭包作为函数的参数,且闭包表达式很长。如:
func someFunctionThatTakesAClosure(closure: () -> Void) {
    // function body goes here
}
//第一种调用方式
someFunctionThatTakesAClosure(closure: {
    // closure's body goes here
})
//第二种调用方式
someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

那么上面的sorted函数的闭包又可以简写为:

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

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

reversedNames = names.sorted { $0 > $1 }
  • 捕获值:闭包可以捕获外部的常量和变量。闭包可以从其body内部引用和修改那些常量和变量的值,即使定义常量和变量的原始范围不再存在。在Swift中,最简单的可以捕获值的闭包形式是嵌套函数,它写在另一个函数的主体内。
  • 闭包是引用类型,要注意循环引用的问题。
  • Escaping 闭包,也就是转义闭包,用@escaping标记闭包。闭包可以转义的一种方法是将其存储在函数外部定义的变量中。 例如,许多启动异步操作的函数都将闭包作为参数用作完成处理程序。 该函数在开始操作后返回,但是直到操作完成后才调用闭包,闭包需要转义,稍后再调用。表达式为:
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    //...
}
  • 通常,闭包通过在闭包主体中使用变量来隐式捕获变量,但是在转义闭包的情况下,您需要明确表示。 如果您要捕获self,请在使用时明确写出self。
  • 如果self是结构体或枚举的实例,则始终可以隐式引用self。 但是,当self是结构体或枚举的实例时,转义的闭包无法捕获对self的可变引用。
  • 自动闭包,可以省略花括号,调用函数时,自动将函数表达式包装为闭包,用@autoclosure标记闭包。表达式为:
func serve(customer customerProvider: @autoclosure () -> String) {
    //...
}
  • 可以同时使用@escaping和@autoclosure标记:
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}

以上就是闭包的全部内容,需要反复多看几遍并敲代码实践一下,这样才更好的理解闭包的使用方法。实话说,本人对于转义闭包理解的还是不够透彻,只是根据官方文档翻译大概了解了基本的用法,后续如果还有更深的理解的时候,再来补充这部分的内容,这里说声抱歉!或者,如果你有更好的理解的话,可以@我,告诉我一下哈,谢谢啦!

最后,喜欢的童鞋可以给个👍哦,嘿嘿~

上一章节:函数

下一章节:枚举

参考文档:Swift - Closures

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335