闭包


  1. 闭包是自包含的函数代码块,可以在代码中被传递和使用。 Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 函数比较相似。
    闭包可以捕获和存储其所在上下文中任意常量和变量的引用。 这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。 

    闭包采取如下三种形式之一:
    • 全局函数是一个有名字但不会捕获任何值的闭包
    • 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
    • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
    Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
    • 利用上下文推断参数和返回值类型
    • 隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
    • 参数名称缩写
    • 尾随(Trailing)闭包语法 

闭包表达式

闭包表达式是一种利用简洁语法构建内联闭包的方式。 闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。 下面闭包表达式的例子通过使用几次迭代展示了 sort(_:) 方法定义和语法优化的方式。 每一次迭代都用更简洁的方式描述了相同的功能。 

//MARK:-闭包表达式:示例-将名字按字母逆序排列
        let names = ["Chris","Alex","Lily","Rose"]
        //1.sort函数:
        let reversed1 = names.sort(backwards)
        print(reversed1)
        
        //2.闭包表达式: 闭包的函数体部分由关键字 in 引入。 该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
        //其实 sort(_:) 方法的整体调用保持不变,一对圆括号仍然包裹住了函数中整个参数集合。而其中一个参数现在 变成了内联闭包(相比于 backwards 版本的代码)
        //需要注意的是内联闭包参数和返回值类型声明与 backwards 函数类型声明相同。 在这两种方式中,都写成了 (s 1: String, s2: String) -> Bool 。 然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
        let reversed2 = names.sort({(s1:String,s2:String)->Bool in
            return s1>s2
        })
        print(reversed2)
        
        //3.根据上下文推断类型(Inferring Type From Context)
        //因为排序闭包函数是作为sort(_:)的参数传入的,swift可以推断其参数和返回值的类型。sorted期望第二个参数是类型为(String,String)->Bool的函数,因此实际上String和Bool类型可以省略,因为所有类型都可以被正确推断,所以,最终的方法可以简化成
        let reversed3 = names.sort({ s1,s2 in return s1>s2})
        print(reversed3)
        
        //4.参数名称缩写:Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过 $0 , $1 , $2 来顺序调用闭包的参数。如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的 类型会通过函数类型进行推断。 in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
        let reversed4 = names.sort({$0>$1}) //$0 和 $1 表示闭包中第一个和第二个 String 类型的参数。
        print(reversed4)
        
        //5:运算符函数 实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。 Swift 的 String 类型定义了关于大于号 ( > ) 的 字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。 而这正好与 sort(_:) 方法的 第二个参数需要的函数类型相符合。 因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号 的字符串函数实现:
        let reversed5 = names.sort(>)
        print(reversed5)
        
        //MARK:-尾随闭包
        //如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。 尾 随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
        //当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。
        let reversed6 = names.sort(){$0>$1}
        print(reversed6)

捕获值

闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。 

Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。 

  1. 例:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal }
return incrementor }
let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen() // 返回的值为10
incrementByTen() // 返回的值为20
incrementByTen() // 返回的值为30

如果您创建了另一个 incrementor ,其会有一个属于自己的独立的 runningTotal 变量的引用。 下面的例子

中, incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有任何联系: 

let incrementBySeven = makeIncrementor(forIncrement: 7) incrementBySeven()
// 返回的值为7
incrementByTen()
// 返回的值为40

闭包是引用类型 

如果您创建了另一个 incrementor ,其会有一个属于自己的独立的 runningTotal 变量的引用。

 下面的例子中, incrementBySevne 捕获了一个新的 runningTotal 变量,该变量和 incrementByTen 中捕获的变量没有任何联系: 


上面的例子中, incrementBySeven 和 incrementByTen 是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。 这是因为函数和闭包都是引用类型。
无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。 上面的例子中, incrementByTen 指向闭包的引用是一个常量,而并非闭包内容本身。
这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:
let alsoIncrementByTen = incrementByTenalsoIncrementByTen()
// 返回的值为50

初学swift,留笔记一份,不喜勿喷,欢迎交流