// 1.闭包表达式 Closure expression
func test_closure_expression() {
// 闭包的定义
/**
用in来分开闭包类型和语句块代码
{
(参数...)->返回值类型 in
闭包代码
}
*/
// 定义一个函数
func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
// 定义一个闭包表达式
var closure = {
(v1: Int, v2: Int)->Int in return v1 + v2
}
// 注意:虽然定义闭包的时候写了参数标签,但是调用的时候不写标签
// print("Closure expression \(sum(1, 2))")
// print("Closure expression \(closure(1, 2))")
// 匿名闭包
// {
// (v1: Int, v2: Int)->Void in
// print("v1 + v2 = \(v1 + v2)")
// }(1, 2)
func exec(fn:(Int, Int)->Int){}
exec { _ , _ in 10} // 当不需要用到参数时,可以使用_忽略
}
test_closure_expression()
// 2.闭包表达式的简化
func test_closure_simplify() {
func exec(v1: Int, v2: Int, sum: (Int, Int)->Int) {
print("closure_simplify->exec:\(sum(v1, v2))")
}
// 正常调用
exec(v1: 1, v2: 2, sum: { (x1: Int, x2: Int) -> Int in
return x1 + x2
})
// 注意:以下一些列省略系编译器自动推断技术
// 简化1
// (由于exec函数已经明确需要两个Int类型参数,因此此处可以省略参数类型和小括号)
// (由于exec函数已经明确返回Int类型的参数,因此可省略返回值类型)
exec(v1: 1, v2: 2, sum: { x1, x2 in
return x1 + x2
})
// 简化2(单条语句,简化掉return)
exec(v1: 1, v2: 2, sum: { x1, x2 in x1 + x2 })
// 简化3(可以省略参数列表和in)
// 使用美元符$+数字表示第几个参数
exec(v1: 1, v2: 2, sum: { $0 + $1 })
// 简化4-1(只有两个参数,直接写个+号,编译器会自动推断出是加法运算)
exec(v1: 1, v2: 2, sum: + ) // 两个参数才可以
// 简化4-2(尾随闭包)
exec(v1: 1, v2: 2) {$0 + $1}
// Test ...
func exec1(v1: Int, v2: Int, v3: Int, sum: (Int, Int, Int)->Int) {
print("closure_simplify->exec:\(sum(v1, v2, v3))")
}
exec1(v1: 1, v2: 2, v3: 3, sum: {$0 + $1 + $2})
exec1(v1: 1, v2: 2, v3: 3) {$0 + $1 + $2}
}
test_closure_simplify()
// 3.尾随闭包
func test_tail_closure() {
// 将闭包作为函数的最后一个参数,可以将该闭包写到小括号外,类似函数体,增强可读性
func exec(v1: Int, v2: Int, v3: Int, sum: (Int, Int, Int)->Int) {
print("test_tail_closure->exec:\(sum(v1, v2, v3))")
}
exec(v1: 1, v2: 2, v3: 3, sum: {$0 + $1 + $2} )
exec(v1: 1, v2: 2, v3: 3) { $0 + $1 + $2 } // 尾随
func exec1(fn: (Int, Int, Int)->Int) {
print("test_tail_closure->exec1:\(fn(1,2,3))")
}
exec1(fn: {$0 + $1 + $2}) // 正常调用
exec1() {$0 + $1 + $2} // 尾随1
exec1 {$0 + $1 + $2} // 尾随2,省略小括号
}
test_tail_closure()
// 4.闭包值捕获
func test_closure_capturing_values() {
// 将闭包作为函数的最后一个参数,可以将该闭包写到小括号外,类似函数体,增强可读性
typealias Fn = (Int)->Int
func getFn()->Fn {
var num = 0
func plus(_ i: Int)->Int {
num += i
return num
}
return plus
}
let fn = getFn()
fn(4) // 4
fn(1) // 5
/** 注意:这里fn其实就是内部的plus
由于num实际上是getFn栈空间的一个局部变量,当 let fn = getFn() 完成后,getFn栈空间会销毁
但是fn使用num的时候,getFn栈空间已经销毁,因此为了保存num的值,fn(plus)会将num的值捕获
为了要捕获num的值,闭包的实现方式将从函数升级为对象
这里的底层实现实际上是在堆空间创建了一个 fn对象,fn的内存结构如下
fn堆空间内存
┏━━━━━━━━━━━━━━━━━━━━━━┓
┃ classInfo ┃
┃ retainCount ┃
┃ var num ┃
┃ plus(Int)->Int{...} ┃
┗━━━━━━━━━━━━━━━━━━━━━━┛
即fn拥有一个成员变量 num 和 一个方法plus,即fn底层实现是使用了对象来实现
当fn中没有捕获外层函数栈空间的局部变量时,不会发生值捕获
当fn内部需要操作的值是全局变量时,也不会发生值捕获
fn发生值捕获的唯一条件就是要捕获的值声明周期过期的情况(一半在函数栈空间的局部变量)
*/
}
test_closure_capturing_values()
// 5.自动闭包
func test_auto_closure() {
// 一个需求:如果第一个数乘5大于20,返回第一个,否则返回第二个乘5
// 使用如下函数实现
func getFirstMoreThan20(_ v1: Int, _ v2: Int)->Int {
return v1 > 20 ? v1 : v2
}
let a = 5
let b = 6
getFirstMoreThan20(a * 5, b * 5)
/** 分析
在函数 getFirstMoreThan20 中,通过第一步发现v1(a*5)大于20.直接返回
此时返现第二个参数 b*5成了无效操作,因此试想内否让b*5延迟执行
即只有当a*5不满足条件时才会发生b*5操作,这无疑会提升时间性能
*/
// 改造函数1
func getFirstMoreThan20_1(_ v1: Int, _ v2: ()->Int)->Int {
return v1 > 20 ? v1 : v2()
}
// 此时的调用变成了这样,能满足我们说的v2延迟执行,即v1满足了条件,v2的b*5不会发生
// 不过该做法看起来不够优雅,还必须要求我们写{},swift提供的自动闭包技术可以优化该操作
getFirstMoreThan20_1(a * 5, {b * 5})
getFirstMoreThan20_1(a * 5) {b * 5}
// 改造函数2(在v2闭包之前添加@autoclosure关键字)
func getFirstMoreThan20_2(_ v1: Int, _ v2: @autoclosure ()->Int)->Int {
return v1 > 20 ? v1 : v2()
}
// 此时调用变得和普通函数没有区别,也看不出 b * 5 会延迟执行
// 但实际上是隐形优化,对外暴露的接口很友好,内部确做了优化操作
// 该操作相当于@autoclosure会在编译阶段自动包装代码,过程如下
// b * 5 --> {b * 5} --> {()->Int in b * 5}
// 这就是自动闭包的优雅之处
getFirstMoreThan20_2(a * 5, b * 5)
}
test_auto_closure()