闭包可以捕获和存储其所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。

闭包有三种形式:
- 全局函数是一个有名字,但不会捕获任何值得闭包
- 嵌套函数是一个有名字,并可以捕获其封闭函数域内值的闭包。
- 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文变量或常量值的匿名闭包。

Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
1. 利用上下文推断参数和返回值类型 。
2. 隐式返回单表达式闭包,即单表达式闭包可以省略return关键字 。
3. 参数名称缩写。
4. 尾随(Trailing)闭包语法。


闭包表达式

  • 闭包表达式是一种利用简洁语法构建内联闭包的方式。
  • 闭包表达式提供了一些语法优化,是的闭包编程变得简单明了。

闭包表达式的语法

  • 闭包表达式语法可以使用常量、变量和inout类型作为参数,不提供默认值。
  • 在参数列表的最后可以使用可变参数。
  • 元组可以作为参数和返回值。
  • 闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
{(paramenters) -> returnType in
    statements
 }

sorted函数

  • sorted函数将已知数组中的值进行排序。
let names = ["Chirs","Slex","Ewa","Narry","Dannile"]
var reversed = names.sorted()
print(reversed)
// ["Chirs", "Dannile", "Ewa", "Narry", "Slex"]

func backWards(S1:String,S2:String)->Bool{
    return S1 < S2
}
var reversed1 = names.sorted(by: backWards)
print(reversed1)
// ["Chirs", "Dannile", "Ewa", "Narry", "Slex"]

运算符函数

  • SwiftString类型定义了关于小于号(<)的字符串实现,其正好与Sorted函数参数类型相符合。因此,可以简单地传递一个小于号,Swift将会自动推断出相关的函数实现。
var reversed2 = names.sorted(by: <)
print(reversed2)
// ["Chirs", "Dannile", "Ewa", "Narry", "Slex"]

根据上下文推断参数类型

  • 因为排序的闭包是作为sotred函数的参数传入的,Swift可以推断其参数和返回值类型。
  • 因为所有的类型都可以被正确推断,返回箭头和围绕在参数周围的括号都可以被省略。
var reversed3 = names.sorted { s1, s2 in
     return s1 > s2
}
print(reversed3)
// ["Slex", "Narry", "Ewa", "Dannile", "Chirs"]

单表达式闭包隐藏返回

  • 单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果。
var reversed4 = names.sorted { s1, s2 in s1 > s2
}
print(reversed4)
// ["Slex", "Narry", "Ewa", "Dannile", "Chirs"]
  • 本例子中,sorted函数的参数类型已经做了明确的表示闭包的返回值必须为布尔类型的值,而闭包函数体内只有一个比较表达式,其返回值是一个布尔类型的数据,因此没有歧义,return关键字可以省略。

参数名称缩写

  • Swift自动为内联函数提供了参数名称的缩写功能,可以利用$0,$1,$2来顺序调用闭包内的参数。
  • 如果在闭包表达式中使用参数名称缩写,可以在闭包参数列表中省略对其的定义,并且对应参数的类型可以通过函数类型进行判断。
  • in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成。
var reversed5 = names.sorted { $0 > $1
}
print(reversed5)
// ["Slex", "Narry", "Ewa", "Dannile", "Chirs"]

尾部闭包

  • 尾部闭包是将闭包表达式书写在函数括号之后,函数支持将其作为最后一个参数调用。
  • 如果只有一个闭包表达式参数,括号()也可以省略。
// 声明一个函数
func someFunctionThatTakesAClosure(_ closure:()->()){
    // 函数体部分
}
// 不使用尾随闭包的方式
someFunctionThatTakesAClosure({
    // 闭包主体部分
})
// 使用尾随闭包的方式
someFunctionThatTakesAClosure(){
    // 闭包主体部分
}
// 由于只有一个闭包表达式参数,括号()也可以省略
someFunctionThatTakesAClosure {
    // 闭包主体部分
}

访问上下文值

  • 闭包可以在其定义的上下文中访问常量或者变量。
  • 即使定义的常量以及变量的作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
func makeIncrementor(amount:Int) -> ()->Int{
    var runningTotal = 0
    func incrementtor()->Int{
        runningTotal += amount
        return runningTotal
    }
    return incrementtor
}
let incrementByTen = makeIncrementor(amount: 10)
incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30

let incrementBySeven = makeIncrementor(amount: 7)
incrementBySeven() // 7
incrementBySeven() // 14

incrementByTen() // 40
  • 对于上述例子而言,incrementtor函数没有获取任何参数,但是在函数体内访问了amountrunningTotal两个变量,由于后续的incrementByTen()没有再修改amount值,故incrementtor函数实际上存储了amout以及runningTotal两个变量的副本,并且随着incrementByTen一起被存储。
  • 如果改变amount值将会重新再生成一个新的incrementtor函数副本。

闭包是引用类型

  • 根据上述例子,incrementByTen以及incrementBySeven均是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。这是因为函数和闭包都是引用类型。
  • 无论将函数或者闭包赋值给一个常量或者变量,实际上都是将常量或变量的值设置为对应函数或者闭包的引用。
let incrementByTen2 = incrementByTen
incrementByTen2() // 50
  • 由此例子可以看出,将闭包赋值给常量或变量,两个值指向同一个闭包。

闭包引起的循环强引用

定义占有列表

  • 在定义闭包的同时,定义占有列表作为闭包的一部分,用以解决闭包的循环强引用。
  • 占有列表的每个元素都是由weakunowned关键字和实例的引用成对组成。每一对都在花括号中,通过逗号隔开。
  • 占有列表放置在闭包参数列表和返回类型之前,代码示例:
class Clourse{
    lazy var someClourse:(Int,String) -> String = {
        [unowned self] (index:Int,stringToProcess:String) ->String in
        // 闭包代码
        return "wangsk"
    }
}
  • 如果闭包没有指定参数列表或者返回类型,则可以通过上下文盘算,可以将占有列表放在闭包开始的地方,跟着使用关键字:in。代码示例:
class Clourse2{
    lazy var someClourse2:() -> String = {
        [unowned self] in
        // 闭包代码
        return "wangsk2"
    }
}

弱引用和无主引用

  • 当闭包和占有的实例总是相互引用,并且同时销毁时,可以将闭包内的占有定义为无主引用unowned
  • 如果,占有引用有时可能置为nil时,需要将闭包内的占有引用定义为weak
  • 如果,占有的引用绝对不会置为nil,应该使用无主引用,而不是弱引用。
class Clourse{
    let name:String
    let text:String?
    lazy var someClourse:() -> String = {
        [unowned self] in
        if self.text != nil{
            return "\(self.name) \(self.text!) \(self.name)"
        }else{
            return "\(self.name)"
        }
    }
    init(name:String,text:String? = nil) {
        self.name = name
        self.text = text
    }
    deinit {
        print("\(name)销毁")
    }
}
var clourse1:Clourse? = Clourse(name: "wangsk", text: nil)
var clourse2:Clourse? = Clourse(name: "wangsk", text: "csdn")
print(clourse1!.someClourse())
print(clourse2!.someClourse())
clourse1 = nil
clourse2 = nil
/*
wangsk
wangsk csdn wangsk
wangsk销毁
wangsk销毁
*/