一:闭包表达式
1:闭包类似匿名函数
1: 闭包类似匿名函数
2: 当一个函数的参数是闭包时,可以传入一个函数名,函数的参数和返回值类型要和闭包的参数和返回值类型相同
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backword(s1:String,s2:String) ->Bool
{
return s1 > s2
}
let names2 = names.sorted(by: backword)
print("2:\(names2)")
2:闭包表达式
{ (parameters) -> returnType in
<#statements#>
}
3:标准闭包表达式
1: 标准的闭包表达式
2: 参数有圆括号括起来
3: 参数类型可类型推断,也可显式标注
4: 返回值类型要显示声明
let names3 = names.sorted(by: { (s1:String,s2:String) -> Bool in return s1 > s2})
print("3:\(names3)")
4:根据上下文推断类型
1: 参数的类型以及返回值的类型都由编译器推断
2: 省略参数的圆括号时,参数的类型一定不能显式类型标注,而要由编译器隐式推断,否则报编译时错误
3: 当返回值的类型可以由编译器推断出来时,返回值得类型可写可不写
let names4 = names.sorted(by: {s1,s2 -> Bool in return s1 > s2})
print("4:\(names4)")
5:单表达式闭包隐式返回
1: 闭包体中只有一行表达式
2: 表达式的返回类型可以推断出来
3: <#return#> 关键字可以省略
let names5 = names.sorted(by: {s1,s2 in s1 > s2})
print("5:\(names5)")
6:参数名称缩写
1: 内联闭包可以提供了参数名缩写功能,直接可以通过
1 $2 来顺序调用闭包的参数
2: 如果在闭包表达式中使用了参数名称缩写,则可以在定义闭包时省略参数列表,不省略时会报错
3: 参数名称缩写的类型会通过函数类型进行推断
4: <#in#> 关键字也可以省略
let names6 = names.sorted(by: {$0 > $1})
print("6:\(names6)")
7:运算符方法
1: String 类型定义了关于大于号(>)的字符串实现 (> 相当于一个方法,用来比较两个字符创的大小)
2: (>)作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值.而这正好与 sorted(by:) 方法的参数需要的函数类型相符合
3: 因此,可以简单地传递一个大于号,Swift 可以自动推断出你想使用大于号的字符串函数实现
let names7 = names.sorted(by: >)
print("7:\(names7)")
二:尾随闭包
1:尾随闭包使用
1: 函数要将闭包参数写在参数列表的最后面才能使用尾随闭包
2: 尾随闭包是写在函数圆括号后面的闭包表达式
3: 使用尾随闭包时不用写出它的参数表示
4: 如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
2:(一:6)使用尾随闭包可以写成下面的样式
let names22 = names.sorted(){$0 > $1}
print("22:\(names22)")
三:值捕获
1:嵌套函数的值捕获
1: 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
2: 下面的函数返回值是() -> Int类型,返回值是一个函数而不是普通的值
3: 返回的函数不接收参数,每次调用返回一个Int数值
4: 单独考虑嵌套函数incrementer(),会发现它有些不同寻常
5:incrementer()函数并没有任何参数,但是在函数体内访问了runningTotal和amount变量
6: 这是因为它从外围函数捕获了runningTotal和amount变量的 引用。
7: 捕获引用保证了runningTotal和amount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal依旧存在。
func makeIncrementer(forAmount amount:Int) -> ()->Int
{
var runningTotal = 0
func incrementer() -> Int
{
runningTotal += amount
return runningTotal
}
return incrementer
}
var inc = makeIncrementer(forAmount: 2)
print(inc())
print(inc())
print(inc())
print(inc())
print(inc())
四:闭包是引用类型
1:闭包和函数都是引用类型
1: 函数和闭包都是引用类型。
2: 无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用
3: 上面的例子中,指向闭包的引用 inc 是一个常量,而并非闭包内容本身。
NB: 当闭包作为类的实例属性时,要注意循环引用(因为闭包是引用类型,类实例也是引用类型,两者相互引用会造成循环引用)
五:逃逸闭包
1: 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸
2: 当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
3: 一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中
e.g: 举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。
这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。
在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。
1:逃逸闭包例子
1:
someFunctionWithEscapingClosure(_:)函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中
2: 如果你不将这个参数标记为@escaping,就会得到一个编译错误。
var comletionHandlers:[()->Void] = []
func someFunctionWithEscapingClosure(comletionHandler: @escaping ()->Void)
{
comletionHandlers.append(comletionHandler)
}
2:逃逸闭包必须显示引用self
1: 将一个闭包标记为
@escaping意味着你必须在闭包中显式地引用self
2: 比如说,在下面的代码中,传递到someFunctionWithEscapingClosure(_:)中的闭包是一个逃逸闭包,这意味着它需要显式地引用self。
3: 相对的,传递到someFunctionWithNonescapingClosure(_:)中的闭包是一个非逃逸闭包,这意味着它可以隐式引用self。
func someFunctionWithNonescapingClosure(closure: ()->Void)
{
closure()
}
class SomeClass {
var x = 10;
func doSomething(){
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 100 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
六:自动闭包
1: 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。
2: 这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
3: 这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
4: 自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。
5: 延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。
1:下面的代码展示了闭包如何延时求值。
1: 尽管在闭包的代码中,
customersInLine的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。
2: 如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。
NB:customerProvider的类型不是String,而是() -> String,一个没有参数且返回值为String的函数。
var customersInLine = ["Chris","Alex","Ewa","Barry","Daniella"]
print(customersInLine.count)
// 打印出 "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出"5"
print("Now serving \(customerProvider())!")
// 打印出"Now serving Chris!"
print(customersInLine.count)
// 打印出"4"
2:闭包作为参数传递给函数时,能获得同样的延时求值行为
1:
serve(customer:)函数接受一个返回顾客名字的显式的闭包。
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"
3:自动闭包作为函数的参数
1: 下面这个版本的 <#serve(customer:#>) 完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为 @autoclosure 来接收一个自动闭包。
2: 现在你可以将该函数当作接受 String 类型参数(而非闭包)的函数来调用。
3: customerProvider 参数将自动转化为一个闭包,因为该参数被标记了 @autoclosure 特性。
NB: 过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure ()->String)
{
print("Now serving \(customerProvider())")
}
serve(customer: customersInLine.remove(at: 0))
// 打印出"Now serving Ewa"
七:逃逸自动闭包
1: 如果你想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure 和 @escaping 属性。
2: 在下面的代码中,collectCustomerProviders(_:) 函数并没有调用传入的 customerProvider 闭包,而是将闭包追加到了 customerProviders 数组中。
3: 这个数组定义在函数作用域范围外,这意味着数组内的闭包能够在函数返回之后被调用
4: 因此,customerProvider 参数必须允许“逃逸”出函数作用域。
// customersInLine i= ["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("Collect \(customerProviders.count) closures.");
// 打印"Collect 2 closures."
for customerProvider in customerProviders
{
print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!"