这是我参与更文挑战的第8天,活动详情查看: 更文挑战
闭包
- 闭包是独立的函数代码块,可以在代码中被传递和使用
- 闭包和
block
的对比- swift中闭包与OC的 block 比较相似
- swift中闭包是一个特殊函数,OC中
block
是一个匿名函数 - 闭包和
block
都经常用于回调 - block表达式
类型:返回值类型(^block的名称)(block的参数) 返回值(^block的名称)(形参列表) = ^( 形参列表){ // 执行代码 }; NSString* (^blockName)(NSString *str) = ^(NSString *str){ return str; }; blockName(@"block") 复制代码
- 闭包表达式
类型:(参数)->(返回值类型) 闭包名称 = { (形参列表) -> return 返回值 in // 执行代码 } let closure = { (str:String) -> String in return str } closure("closure") 复制代码
闭包类型
- 无参数无返回值
let closure = {()->Void in print("closure") } closure() 复制代码
- 有参数无返回值
let closure = {(str:String) -> Void in print(str) } closure("closure") 复制代码
- 无参数有返回值
let closure = {() -> String in return "closure" } closure() 复制代码
- 有参数有返回值
let closure = { (str:String) -> String in return str } closure("closure") 复制代码
闭包表达式
下面例子通过使用几次迭代展示了 sorted(by:)
方法的定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能
-
通过函数处理
sorted(by:)
方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔值来进行排序。排序闭包函数类型需为:(Int, Int) -> Bool
let numbers = [1,9,2,8,3,7,4,6]; let numArr = numbers.sorted(by: callback) func callback(_ numA:Int, _ numB:Int) -> Bool { return numA < numB } print(numArr) log: [1, 2, 3, 4, 6, 7, 8, 9] 复制代码
-
通过闭包表达式处理
内联闭包参数和返回值类型声明与
callback(_:_:)
函数类型声明相同let numbers = [1,9,2,8,3,7,4,6]; let numArr = numbers.sorted { (numA:Int, numB:Int) -> Bool in return numA < numB } print (numArr) log: [1, 2, 3, 4, 6, 7, 8, 9] 复制代码
-
根据上下文推断类型
sorted(by:)
方法被一个Int
数组调用,因此其参数必须是(Int, Int) -> Bool
类型的函数,因为所有的类型都可以被正确推断,所以返回箭头(->
)和围绕在参数周围的括号也可以被省略let numbers = [1,9,2,8,3,7,4,6]; let numArr = numbers.sorted { numA,numB in numA < numB } print(numArr) log [1, 2, 3, 4, 6, 7, 8, 9] 复制代码
-
单表达式闭包隐式返回
sorted(by:)
方法的参数类型明确了闭包必须返回一个Bool
类型值,因为闭包函数体只包含了一个单一表达式(s1 > s2)
,该表达式返回Bool
类型值,因此这里没有歧义,return
关键字可以省略let numbers = [1,9,2,8,3,7,4,6]; let numArr = numbers.sorted { numA,numB in numA < numB } print(numArr) log: [1, 2, 3, 4, 6, 7, 8, 9] 复制代码
-
参数名称缩写
可以直接通
$0,$1,$2
来顺序调用闭包的参数,以此类推。如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。in
关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成let numbers = [1,9,2,8,3,7,4,6]; let numArr = numbers.sorted {$0 < $1} print(numArr) log: [1, 2, 3, 4, 6, 7, 8, 9] 复制代码
-
运算符方法
Swift 的
Int
类型定义了关于大于号(>
)的实现,其作为一个函数接受两个Int
类型的参数并返回Bool
类型的值。而这正好与sorted(by:)
方法的参数需要的函数类型相符合。因此,可以简单地传递一个大于号let numbers = [1,9,2,8,3,7,4,6]; let numArr = numbers.sorted(by: <) print(numArr) log: [1, 2, 3, 4, 6, 7, 8, 9] 复制代码
尾随闭包
- 如果闭包是函数的最后一个参数,那么可以将闭包写在括号
()
后面func closure(num:Int, a:() -> ()) -> Void { print("多个参数") } closure(num: 3) { print("闭包为最后一个参数") } 复制代码
- 如果函数只有一个参数且为闭包,那么括号
()
可以不写func closure(a:() -> ()) -> Void { print("只有一个参数且在最后") } closure { print("闭包为最后一个参数") } 复制代码
值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
func add(num:Int) -> ()->Int {
var value = 0
func result() -> Int{
value += num
return value
}
/*
这有一个叫做 add 的函数,其包含了一个叫做 result 的嵌套函数
嵌套函数result()从上下文中捕获了两个值,value 和 num
捕获这些值之后,add 将 result 作为闭包返回
每次调用 result 时,其会以 num 作为增量增加 value 的值
*/
return result
}
复制代码
-
嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量,并且保证了在下一次执行函数时,之前捕获的值依旧存在
func add(num:Int) -> ()->Int { var value = 0 func result() -> Int{ value += num return value } return result } let result = add(num: 10) print(result()) //10 print(result()) //20 print(result()) //30 复制代码
-
同一个方法中的变量, 会被绑定到属于自己的变量
func add(num:Int) -> ()->Int { var value = 0 func result() -> Int{ value += num return value } return result } //如果你创建了另一个result1,它会有属于自己的引用,指向一个全新、独立的value变量 let result = add(num: 10) print(result()) //10 //再次调用原来的result会继续增加它自己的value变量,该变量和result1中捕获的变量没有任何联系 print(result()) //40 复制代码
闭包是引用类型
-
函数和闭包都是引用类型
你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用
//这两个常量或变量都引用相同的闭包 let method = result 复制代码
逃逸闭包
- 一个传入函数的闭包如果在函数执行结束之后才会被调用,那么这个闭包就叫做逃逸闭包。通俗点讲,不在当前方法中使用闭包,而是在方法之外使用
- 定义函数的参数为逃逸闭包时,只需要在参数名之前标注
@escaping
,用来指明这个闭包是允许“逃逸”出这个函数的 - 将一个闭包标记为
@escaping
意味着你必须在闭包中显式地引用self
var result: ()->Void = {} var str = "" func showA(closure: @escaping () -> Void) { result = closure } func showB(closure: () -> Void) { closure() } func doSomething() { showA {str = "我是逃逸闭包"} showB {str = "我是普通闭包"} } doSomething() print(str) //我是普通的闭包 result() print(str) //我是逃逸的闭包 复制代码
逃逸闭包是在函数执行之后再执行,于是这段代码最后输出“我是逃逸的闭包”
自动闭包
-
自动闭包: 自动创建一个闭包用来包裹一个表达式,这种闭包不接受任何参数,当闭包被调用时,返回包裹在闭包中的表达式的值
-
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行
-
这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包
var arr = ["a","b","c"] print(arr.count) //3 let block = { arr.remove(at: 0) } print(arr.count) //3 print(block()) //a print(arr.count) //2 复制代码
-
将闭包作为参数传递给函数时,同样可以延时求值
函数接受一个显式闭包类型的参数
func delete(closure: ()->String){ print(closure()) } var arr = ["a","b","c"] delete(closure:{arr.remove(at: 0)}) log: a 复制代码
通过将参数标记为
@autoclosure
来接收一个自动闭包,该函数接受的是String
类型参数而非闭包func delete(closure: @autoclosure ()-> String){ print(closure()) } var arr = ["a","b","c"] delete(closure:arr.remove(at: 0)) log: a 复制代码