闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
闭包表达式语法有如下的一般形式:
1 { 2 (parameters) -> returnType in 3 statements 4 }
- 这里的参数(parameters),可以是in-out(输入输出参数),但不能设定默认值。如果是可变参数,必须放在最后一位,不然编译器报错。元组也可以作为参数或者返回值。
- 闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始
例如:
1 let sum:(Int , Int) -> Int = {(a : Int ,b : Int) -> Int in 2 return a + b 3} 4 print(sum(3,4))
由于可以推断出闭包的参数和返回值类型,所以闭包表达式可以表达为:
1 let sum:( Int , Int ) -> Int = { a , b in 2 return a + b 3 }
单表达式闭包隐式返回
单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
1 let sum:( Int , Int ) -> Int = { a , b in 2 a + b 3 }
有参数没有返回值的写法
1let say0:(String) ->Void = { 2 (name: String) in 3 print("hi \(name)") 4} 5say0("cdh") 6//输出结果: hi cdh
没有参数没有返回值写法
1let say1:() ->Void = { 2 () -> () in // 当没有参数也没有返回值是这行都可以省略 3 print("hi cdh") 4} 5say1() 6//输出结果: hi cdh
闭包的声明
Swift用typealias声明闭包数据类型,类似于Objective-C中的typedef
1typealias TestBlcok = (Int , Int) -> Int 2 3let test : TestBlcok = {( a , b ) in a + b} 4 5let result = test( 12 , 20 ) 6 7计算结果是 32
尾随闭包
若将闭包作为函数最后一个参数,可以省略参数标签,然后将闭包表达式写在函数调用括号后面
1func testFunction(testBlock: ()->Void){ 2 //这里需要传进来的闭包类型是无参数和无返回值的 3 testBlock() 4} 5//正常写法 6testFunction(testBlock: { 7 print("正常写法") 8}) 9//尾随闭包写法 10testFunction(){ 11 print("尾随闭包写法") 12} 13//也可以把括号去掉,也是尾随闭包写法。推荐写法 14testFunction { 15 print("去掉括号的尾随闭包写法") 16}
值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。”
例如:
1func makeIncrementer(forIncrement amount: Int) -> () -> Int { 2 var runningTotal = 0 3 func incrementer() -> Int { 4 runningTotal += amount 5 return runningTotal 6 } 7 return incrementer 8} 9
makeIncrementor 返回类型为 () -> Int。这意味着其返回的是一个函数,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 Int 类型的值。
makeIncrementer(forIncrement:) 函数定义了一个初始值为 0 的整型变量 runningTotal,用来存储当前总计数值。该值为 incrementor 的返回值。
makeIncrementer(forIncrement:) 有一个 Int 类型的参数,其外部参数名为 forIncrement,内部参数名为 amount,该参数表示每次 incrementor 被调用时 runningTotal 将要增加的量。makeIncrementer 函数还定义了一个嵌套函数 incrementor,用来执行实际的增加操作。该函数简单地使 runningTotal 增加 amount,并将其返回。
如果我们单独考虑嵌套函数 incrementer(),会发现它有些不同寻常:
1func incrementer() -> Int { 2 runningTotal += amount 3 return runningTotal 4}
incrementer() 函数并没有任何参数,但是在函数体内访问了 runningTotal 和 amount 变量。这是因为它从外围函数捕获了 runningTotal 和 amount 变量的引用。捕获引用保证了 runningTotal 和 amount 变量在调用完 makeIncrementer 后不会消失,并且保证了在下一次执行 incrementer 函数时,runningTotal 依旧存在。
注意 :为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。 Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
闭包引起的循环强引用
1//定义一个全局变量: 2 3 var a: Int = 9 4 5 var closure: ()->Void = { 6 7 /** 8 9 默认闭包会对它访问的对象执行强引用。 10 这个地方访问了self ,导致调用 该闭包的时候引用计数+1 11 如果闭包属性中没有直接或者间接访问self,就不会产生循环强引用。 12 可以使用unowned或者weak 来修饰self。 13 当然两种存在区别。Swift中的weak、unowned对应Objective-C中的weak、unsafe_unretained, 14 这表明前者在对象释放后 会自动将对象置为nil,而后者依然保持一个“无效的”引用,如果此时调用这 15 个“无效的”引用将会引起程序崩溃。对于后者的使用,我们必须保证访问的时候对象没有释放。 16 相反,weak则友好一点我们也更加习惯使用它, 17 18 */ 19 20 [weak self] in 21 22 print("a=\(self.a)") 23 24}
逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
自动闭包
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。