语法表达式
1 2 3 4 | 一般形式:{ (parameters) -> returnType in statements } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //一般形式 let calAdd:(Int,Int)->(Int) = { (a:Int,b:Int) -> Int in return a + b } print(calAdd(100,150)) //Swift可以根据闭包上下文推断参数和返回值的类型,所以上面的例子可以简化如下 let calAdd2:(Int,Int)->(Int) = { a,b in //也可以写成(a,b) in return a + b } print(calAdd2(150,100)) //上面省略了返回箭头和参数及返回值类型,以及参数周围的括号。当然你也可以加括号,为了好看点,看的清楚点。(a,b) //单行表达式闭包可以隐式返回,如下,省略return let calAdd3:(Int,Int)->(Int) = {(a,b) in a + b} print(calAdd3(50,200)) //如果闭包没有参数,可以直接省略“in” let calAdd4:()->Int = { return 100 + 150} print( "....\(calAdd4())" ) //这个写法,我随便写的。打印出“我是250” //这个是既没有参数也没返回值,所以把return和in都省略了 let calAdd5:()->Void = {print( "我是250" )} calAdd5() |
-
归纳
闭包类型是由参数类型和返回值类型决定,和函数是一样的。比如上面前三种写法的闭包的闭包类型就是(Int,Int)->(Int)
,后面的类型分别是()->Int
和()->Void
。分析下上面的代码:let calAdd:(add类型)
。这里的add类型就是闭包类型 (Int,Int)->(Int)
。意思就是声明一个calAdd常量,其类型是个闭包类型。
"="右边是一个代码块,即闭包的具体实现,相当于给左边的add常量赋值。兄弟们,是不是感觉很熟悉了,有点像OC中的block代码块。
起别名
1 2 3 4 5 6 7 8 9 | typealias AddBlock = (Int, Int) -> (Int) let Add:AddBlock = { (c,d) in return c + d } let Result = Add(100,150) print( "Result = \(Result)" ) |
尾随闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | func testFunction(testBlock: ()->Void){ //这里需要传进来的闭包类型是无参数和无返回值的 testBlock() } //正常写法 testFunction(testBlock: { print( "正常写法" ) }) //尾随闭包写法 testFunction(){ print( "尾随闭包写法" ) } //也可以把括号去掉,也是尾随闭包写法。推荐写法 testFunction { print( "去掉括号的尾随闭包写法" ) } |
值捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 | func captureValue(sums amount:Int) -> ()->Int{ var total = 0 func incrementer()->Int{ total += amount return total } return incrementer } print(captureValue(sums: 10)()) print(captureValue(sums: 10)()) print(captureValue(sums: 10)()) //打印"10 10 10" |
这里没有值捕获的原因是,没有去用一个常量或变量去引用函数,所以每次使用的函数都是新的。有点类似于OC中的匿名对象。
1 2 3 4 5 | let referenceFunc = captureValue(sums: 10) print(referenceFunc()) print(referenceFunc()) print(referenceFunc()) //打印"10 20 30" |
这里值捕获了,是因为函数被引用了,所以没有立即释放掉。所以函数体内的值可以被捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 | func captureValue2(sums amount:Int) -> ()->Int{ var total = 0 let AddBlock:()->Int = { total += amount return total } return AddBlock } let testBlock = captureValue2(sums: 100) print(testBlock()) print(testBlock()) print(testBlock()) |
由上面的例子都可以证得,函数和闭包都是引用类型。
逃逸闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | //例1 func doSomething(some: @escaping () -> Void){ //延时操作,注意这里的单位是秒 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) { //1秒后操作 some() } print( "函数体" ) } doSomething { print( "逃逸闭包" ) } //例2 var comletionHandle: ()->String = { "约吗?" } func doSomething2(some: @escaping ()->String){ comletionHandle = some } doSomething2 { return "叔叔,我们不约" } print(comletionHandle()) //将一个闭包标记为@escaping意味着你必须在闭包中显式的引用self。 //其实@escaping和self都是在提醒你,这是一个逃逸闭包, //别误操作导致了循环引用!而非逃逸包可以隐式引用self。 //例子如下 var completionHandlers: [() -> Void] = [] //逃逸 func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } //非逃逸 func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } } |
自动闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 | var array = [ "I" , "have" , "a" , "apple" ] print(array.count) //打印出"4" let removeBlock = {array.remove(at: 3)} //测试了下,这里代码超过一行,返回值失效。 print(array.count) //打印出"4" print( "执行代码块移除\(removeBlock())" ) //打印出"执行代码块移除apple" 这里自动闭包返回了apple值 print(array.count) //打印出"3" 5. 非逃逸闭包(Nonescaping Closures) 一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。可以在参数名之前标注@noescape ,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注@noescape 能使编译器知道这个闭包的生命周期. 像刚才的数组的sort(_:) 函数中的参数就定义成了非逃逸闭包, public func sort(@noescape isOrderedBefore: (Self.Generator.Element, Self.Generator.Element) -> Bool) -> [Self.Generator.Element] 你可能会问什么时候会出现逃逸闭包呢?举个例子:很多启动异步操作的函数接受一个闭包参数作为 completion handler 。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。 非逃逸闭包和逃逸闭包讲的不是执行先后顺序吧,非逃逸是指你的闭包不能在函数外单独调用,只能在函数内部调用,函数调用完成后,那个闭包也就结束了。 下面举个逃逸闭包的例子: //声明一个存放函数的数组
var functionArray: [() -> Void] = []
//定义一个接收闭包参数的函数,如果定义非逃逸函数 func doSomething(@noescape paramClosure:() -> Void) 就会编译错误
func doSomething(paramClosure:@escaping () -> Void){
//把参数放入数组中,用于逃逸调用
functionArray.append(paramClosure)
}
//调用函数
doSomething(paramClosure: {print("Hello world")})
doSomething(paramClosure: {print("Hello LvesLi")})
//逃逸调用闭包
for closurePrama in functionArray {
print("\(closurePrama)")
} |