第十四章 Swift 闭包

2019-06-05  本文已影响0人  我有小尾巴快看

Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似,全局函数和嵌套函数其实就是特殊的闭包。

闭包的形式有:

全局函数 嵌套函数 闭包表达式
有名字但不能捕获任何值。 有名字,也能捕获封闭函数内的值。 无名闭包,使用轻量级语法,可以根据上下文环境捕获值。

Swift中的闭包有很多优点:

  1. 根据上下文推断参数和返回值类型
  2. 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return)
  3. 可以使用简化参数名,如$0, $1(从0开始,表示第i个参数...)
  4. 提供了尾随闭包语法

闭包表达式

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

//语法
{(parameters) -> ReturnType in
    statements
    return  ReturnType
}
let studname = { print("Swift 闭包实例。") }
studname()

1. 参数名称缩写

闭包的外部参数名必须为_,您可以直接通过$0,$1,$2来顺序使用闭包的参数。

let names = ["AT", "AE", "D", "S", "BE"]

var reversed = names.sorted( by: { $0 > $1 } ) // $0表示当前循环到元素,$1为$0后面一个,以此类推。
print(reversed) // ["S", "D", "BE", "AT", "AE"]

如果你在闭包中使用参数名称缩写, 您可以在闭包参数列表中省略对其定义, 并且对应参数名称缩写的类型会通过函数类型进行推断。in 关键字同样也可以被省略.

2. 运算符函数

Swift支持一些更简短的方式来使用闭包中的比较。

Swift 的String类型定义了关于大于号 > 的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。 而这正好与sort(_:)方法的第二个参数需要的函数类型相符合。 因此,您可以简单地传递一个大于号,Swift可以自动推断出您想使用大于号的字符串函数实现:

let names = ["AT", "AE", "D", "S", "BE"]

var reversed = names.sorted(by: >)
print(reversed) // ["S", "D", "BE", "AT", "AE"]

3. 尾随闭包

尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
  // 闭包主体部分
}
let names = ["AT", "AE", "D", "S", "BE"]

//尾随闭包
var reversed = names.sorted() { $0 > $1 } // sort() 后的 { $0 > $1} 为尾随闭包。
print(reversed) // ["S", "D", "BE", "AT", "AE"]

注意: 如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉。
reversed = names.sorted { $0 > $1 }

4. 捕获值

闭包可以在其定义的上下文中捕获常量或变量,如果是对象类型则会进行只读复制,其他类型会进行复制,和OC一样。

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

func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

/*
incrementor函数并没有获取任何参数,但是在函数体内访问了runningTotal和amount变量。这是因为其通过捕获在包含它的函数体内已经存在的runningTotal和amount变量而实现。
由于没有修改amount变量,incrementor实际上捕获并存储了该变量的一个副本,而该副本随着incrementor一同被存储。
所以我们调用这个函数时会累加:
*/

let incrementByTen = makeIncrementor(forIncrement: 10)
print(incrementByTen()) // 10
print(incrementByTen()) // 20
print(incrementByTen()) // 30

5. 闭包是引用类型

上面的例子中,incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值,这是因为函数和闭包都是引用类型。

无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。 上面的例子中,incrementByTen指向闭包的引用是一个常量,而并非闭包内容本身。

这也意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:

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
incrementByTen() // 40

let alsoIncrementByTen = incrementByTen
print(alsoIncrementByTen()) // 50

6. 无主引用和弱引用

为了避免引用循环,Swift为闭包提供了两种方式打断循环。
unowned:无主引用,被引用的对象不会增加引用计数,并且一定有值(如果原本就有的话),当对象被释放后,再次调用将会抛出异常。
weak:弱引用,被引用的对象会通过弱引用的方式被引用,此时对象类型变更为可选类型,对象被释放后不会有异常出现。

let block1 = { [unowned self] in
    print(self.view.frame)
}

let block2 = { [weak self] in
    print(self?.view.frame)
}

7.逃逸闭包

闭包分为逃逸闭包非逃逸闭包

逃逸闭包才需要使用unownedweak来避免引用循环。

8. 作为参数和返回值

闭包可以作为参数传递,和OC用法一样。
Swift额外支持了将闭包作为返回值来返回,使得代码能够更加紧凑。

9. 常见的闭包使用场景

lazy var label: UILabel = {
    return UILabel()
}()
var index: Int {
    return 1
}
let block = { [unowned self] (a: Int, b: Int) -> Int in
    return Int(self.view.frame.minX) + a + b
}

block(1,2)
func eat(food: ((String,Int) -> ())?) {

}

eat { (food, count) in //尾随闭包

}
func eat(food: ((String,Int) -> ())?) -> ((String) -> ()) {
    return { (key) -> () in
         print(key)
    }
}

let block = eat { _ in // 参数可用`_`无视
}

block("Apple") // Apple
上一篇下一篇

猜你喜欢

热点阅读