界说

  • 闭包是一个自包括的函数代码块,能够在代码中被传递和引证
  • 闭包能够捕获和存储其所在上下文中恣意常量和变量的引证**。

闭包的语法有三种办法:大局函数、嵌套函数和闭包表达式。

  • 大局函数是一个有姓名但不会捕获任何值的闭包
  • 潜逃函数是一个有姓名并能够捕获其关闭函数域内值的闭包
  • 闭包表达式是一个运用轻量级语法所写的能够捕获其上下文中变量或常量值的匿名闭包

闭包表达式

闭包表达式的一般办法

{ (parameters) -> return type in
statements
}

以数组的sorted(by:)办法为例

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

写成一行

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

依据上下文揣度类型

  • sorted(by:)办法被一个字符串数组调用,Swift 能够揣度其参数和回来值的类型,因此其参数必须是 (String, String) -> Bool
  • 这意味着(String, String)Bool 类型并不需求作为闭包表达式界说的一部分。由于一切的类型都能够被正确揣度,回来箭头(->)和围绕在参数周围的括号也能够被省掉:
names.sorted(by: { s1, s2 in return s1 > s2})

单表达式闭包的隐式回来

  • 单行表达式闭包能够经过省掉 return 关键字来隐式回来单行表达式的结果
names.sorted(by: { s1, s2 in s1 > s2})

参数称号缩写

  • Swift 主动为内联闭包提供了参数称号缩写功用,你能够直接经过 $0$1$2 来次序调用闭包的参数,以此类推。
  • 闭包承受的参数的数量取决于所运用的缩写参数的最大编号。
  • in 关键字也相同能够被省掉,由于此刻闭包表达式完全由闭包函数体构成:
names.sorted(by: {s1 > s2})

运算符办法

  • Swift 的 String 类型界说了关于大于号(>)的字符串完结,其作为一个函数承受两个 String 类型的参数并回来 Bool 类型的值。而这正好与 sorted(by:) 办法的参数需求的函数类型相符合。因此,你能够简略地传递一个大于号,Swift 能够主动揣度找到体系自带的那个字符串函数的完结:
names.sorted(by: >)

跟随闭包

跟随闭包是一种特殊的闭包语法,它能够在函数调用的括号外部以简洁的办法提供闭包作为函数的最终一个参数。
运用跟随闭包的优势在于增加了代码的可读性和简洁性。当闭包作为函数的最终一个参数时,将闭包放在括号外部,能够使函数调用更加清晰,更接近于自然语言的阅览次序。

func calculate(a: Int, b: Int, closure: (Int, Int) -> Int) {
    let result = closure(a, b)
    print(result)
}
// 调用函数时运用跟随闭包
calculate(a: 5, b: 3) { (x, y) -> Int in
    return x + y
}
// 假如闭包只包括一个表达式,能够省掉 return 关键字
calculate(a: 5, b: 3) { (x, y) in
    x + y
}
// 省掉参数的类型和括号
calculate(a: 5, b: 3) { x, y in
    x + y
}
// 运用 $0, $1 等缩写办法替代参数名
calculate(a: 5, b: 3) {
    $0 + $1
}

假如一个函数承受多个闭包,需求省掉第一个跟随闭包的参数标签,并为其余跟随闭包增加标签。

值捕获

闭包能够在其被界说的上下文中捕获常量或变量。即便界说这些常量和变量的原效果域现已不存在,闭包依然能够在闭包函数体内引证和修正这些值。

能够捕获值的闭包最简略的办法是嵌套函数,也便是界说在其他函数的函数体内的函数。嵌套函数能够捕获其外部函数一切的参数以及界说的常量和变量。

留意:

假如将闭包赋值给一个类实例的属性,而且该闭包经过拜访该实例或其成员捕获了该实例,将会形成一个循环引证。

捕获列表

默认情况下,闭包会捕获附近效果域中的常量和变量,并运用强引证指向它们。你能够经过一个捕获列表来显现指定它的捕获行为。

捕获列表在参数列表之前,由中括号括起来,里边是由逗号分隔的一系列表达式。一旦运用了捕获列表,就必须运用in关键字,即便省掉了参数名、参数类型和回来类型。

捕获列表中的项会在闭包创立时被初始化。每一项都会用闭包附近效果域中的同名常量或许变量的值初始化。例如下面的代码实例中,捕获列表包括a而不包括b,这将导致这两个变量有不同的行为。

var a = 0
var b = 0
let closure = { [a] in
    print(a, b)
}
a = 10
b = 10
closure()
// 打印“0 10”

假如捕获列表中的值是类类型,能够运用weakunowned来修饰它,闭包会分别用弱引证、无主引证来捕获该值:

myFunction { print(self.title) }                    // 隐式强引证捕获
myFunction { [self] in print(self.title) }             // 显式强引证捕获
myFunction { [weak self] in print(self!.title) }   // 弱引证捕获
myFunction { [unowned self] in print(self.title) } // 无主引证捕获

在捕获列表中,也能够将恣意表达式的值绑定到一个常量上。该表达式会在闭包被创立时进行求值,闭包会按照制定的引证类型来捕获表达式的值:

// 以弱引证捕获 self.parent 并赋值给 parent
myFunction { [weak parent = self.parent] in print(parent!.title) }

处理闭包的循环强引证

在界说闭包时一起界说捕获列表作为闭包的一部分,经过这种办法能够处理闭包和类实例之间的循环强引证。捕获列表界说了闭包体内捕获一个或许多个引证类型的规矩。跟处理两个类实例间的循环强引证相同,声明每个捕获的引证为弱引证或无助引证,而不是强引证。应当依据代码联系来决定运用弱引证仍是无主引证。

运用规矩

  • 在闭包和捕获的实例总是互相引证而且一起销毁时,将闭包内的捕获界说为无主引证

  • 相反,在被捕获的引证可能会变为nil,将闭包内的捕获界说为弱引证,弱引证总是可选类型,而且当引证的实例被销毁后,弱引证的值会主动置为nil。这使咱们能够在闭包体内查看它们是否存在

留意

假如被捕获的实例肯定不会变为nil,应该运用无主引证,而不是弱引证。

闭包是引证类型

无论你将函数和闭包赋值给一个常量仍是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引证

逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数之后才被执行,称该闭包从函数中逃逸

在参数名之前标注@escaping指明这个闭包是允许逃逸出这个函数。

一种能使闭包”逃逸”出函数的办法是,将这个闭包包存在一个函数外部界说的变量中。比方:许多异步操作的函数承受一个闭包参数作为completion handler。这类函数会在异步操作开端之后马上回来,但是闭包直到异步操作完毕后才会被调用。这种情况下,闭包需求”逃逸”出函数,由于闭包需求在函数回来之后被调用:

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

留意

将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引证 self

主动闭包

主动闭包是一种主动创立的闭包,用于包装传递给函数作为参数的表达式。这种闭包不承受任何参数,当它被调用的时候,会回来被包装在其间的表达式的值。这种便利语法让你能够省掉闭包的花括号,用一个一般的表达式来替代显式的闭包。

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

总结

Swift 的闭包有以下几个主要的知识点:

  1. 闭包表达式(Closure Expressions):闭包表达式是一种在简略的几行代码中完结自包括的功用代码块。比方数组的排序办法 sorted(by:)
  2. 跟随闭包(Trailing Closures):假如你需求将一个很长的闭包表达式作为一个函数的最终一个参数,运用跟随闭包是很有用的。跟随闭包是一个书写在函数或办法的括号之后的闭包表达式。
  3. 值捕获(Value Capturing):闭包能够在其界说的上下文中捕获和存储任何常量和变量的引证。这便是所谓的闭包的值捕获特性。
  4. 闭包是引证类型(Closures Are Reference Types):无论你将函数/办法或闭包赋值给一个常量仍是变量,你实际上都是将引证赋值给了一个常量或变量。假如你对这个引证进行了修正,那么它将影响原始数据。
  5. 逃逸闭包(Escaping Closures):一个闭包能够“逃逸”出被界说的函数并在函数回来后被调用。逃逸闭包通常存储在界说了该闭包的函数的外部。
  6. 主动闭包(Autoclosures):主动闭包能让你延迟处理,由于代码段不会被执行直到你调用这个闭包。主动闭包很有用,用来包装那些需求被延迟执行的代码。

Swift 闭包和OC Block

类似点:

  1. 都是能够捕获和存储其所在上下文的变量和常量的引证的代码块。
  2. 都能够作为参数传递给函数或办法,或许作为函数或办法的回来值。
  3. 都能够在代码块中界说局部变量和常量。
  4. 都能够拜访其被创立时所处的上下文环境。

差异:

  1. 语法:Swift 的闭包语法更简洁明了,运用大括号 {} 来界说闭包,而 Objective-C 的 Block 语法相对复杂,运用 符号和大括号 ^{} 来界说 Block。
  2. 内存办理:Objective-C 的 Block 对捕获的目标默认运用强引证,需求留意防止循环引证;而 Swift 的闭包对捕获的变量默认运用强引证,但经过运用捕获列表(capture list)能够完结对捕获变量的弱引证或无引证。
  3. 类型揣度:Swift 的闭包关于参数和回来值的类型具有类型揣度的才能,能够省掉类型注解;而 Objective-C 的 Block 需求明确指定参数和回来值的类型。
  4. 逃逸闭包:Swift 能够将闭包标记为 @escaping,表示闭包可能会在函数回来之后才被调用;而 Objective-C 的 Block 默认是能够在函数回来后被调用的。