Swift3.0_闭包(Closure)

96
Michael杨
2016.12.15 08:53* 字数 1412

闭包基础

** 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似 **

如果要使用闭包,首先要闭包赋值给变量或者常量
定义一个可以持有闭包的变量

var multiplyClosure: (Int, Int) -> Int

将闭包赋值给前面定义的变量

multiplyClosure = { (a: Int, b: Int) -> Int in
      return a * b
}

使用定义好的闭包

let result = multiplyClosure(4, 2)
// result is 8

闭包函数的简易写法

  1. 如果闭包只声明了一个返回值,那么可以将return省略掉
multiplyClosure = { (a: Int, b: Int) -> Int in
        a*b
}
  1. 可以省略所有的类型信息,因为前面定义的变量中已经指明了闭包的参数类型和返回值类型
multiplyClosure = { (a, b) in
        a*b
}
  1. 省略掉所有参数列表。闭包中可以通过$加一个从0开始的数字来表示参数。
    不过这种写法个人认为可读性不强,不建议这么写
multiplyClosure = {
      $0 * $1
}

下面来通过一个例子来简单的使用一下这几种写法

首先定义一个函数,其中第三个参数是一个函数

func operateOnNumbers(_ a: Int, _ b: Int, operation: (Int,Int) -> Int) -> Int{
    let result = operation(a, b)
    print(result)
    return result
}

接着调用这个函数

let addClosure = { (a: Int, b: Int) in   //定义一个闭包  
        a+b
}
operateOnNumbers(4, 2, operation: addClosure)

当然,operation这个参数,也可以直接传函数,传闭包是因为闭包也是没有名称的函数,对于operateOnNumbers来说,调用闭包或者函数对它来说没有区别

func addFunction(_ a: Int, _ b: Int) -> Int {
return a + b}
operateOnNumbers(4, 2, operation: addFunction)

下面来看几种简易的写法

operateOnNumbers(3, 4, operation: {$0 * $1}) //上文的第三种简写

operateOnNumbers(3, 4, operation: {(a,b) in
      a * b   //第二种,省略类型的简写
})
operateOnNumbers(3, 4, operation: {
    (a: Int, b: Int) -> Int in //第一种省略return
    a * b
})
operateOnNumbers(3, 4, operation:  * ) //最简易的写法

尾随闭包

operateOnNumbers(3, 4){
    $0 * $1   // 这种简写比较特殊,只有当闭包是作为函数的最后一个参数的时候才可以这么用
}

定义无参数无返回值的闭包

let voidClosure: () -> Void = {
 print("Swift Apprentice is awesome!")}
voidClosure()

闭包--值捕获

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

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

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

incrementer闭包可以捕获makeIncrementer(forIncrement:)中的runningTotal和amount变量的引用,捕获引用保证了runningTotal和amount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal依旧存在。

let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen()// 返回的值为10
incrementByTen()// 返回的值为20
incrementByTen()// 返回的值为30

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

incrementByTen()// 返回的值为40

可以看到incrementByTen中的变量和incrementBySeven中的变量没有任何联系,而且,他们分别保持着对变量的引用。

闭包是引用类型

** 函数和闭包都是引用类型 **

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

如果将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTenalso
IncrementByTen()// 返回的值为50

逃逸闭包(Escaping Closures)

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,称闭包从函数中逃逸。
当定义接受闭包作为参数的函数时,可以在参数名之前标注@escaping,用来指明这个闭包是允许“逃逸”出这个函数的。

一种能使闭包逃逸出函数的方法是,将这个闭包保存在函数外部定义的变量中。
例如:很多启动异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包知道异步操作结束后才会被调用。
这种情况,闭包需要逃逸出函数,因为闭包需要在函数返回之后被调用。

var completionHandlers: [() -> Void] = []func  someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
 completionHandlers.append(completionHandler)
}

如果不加@escaping关键字,将会得到一个编译错误

将一个闭包标记为@escaping意味着你必须在闭包中显示地引用self

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneEscapingClosure(closure: () -> Void){
    closure()
}

class SomeClass{
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure {
            self.x = 100
        }
        someFunctionWithNoneEscapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x) //打印出200

completionHandlers.first?()
print(instance.x) //打印出100

自动闭包

自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。

自动闭包能够延迟求职,直到调用这个闭包,代码段才回被执行。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]print(customersInLine.count)
// 打印出 "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"

只有当闭包customerProvider执行时,才会执行remove操作

利用闭包来自定义排序方法

数组的排序

let names = ["ZZZZZZ", "BB", "A", "CCCC", "EEEEE"]names.sorted()
// ["A", "BB", "CCCC", "EEEEE", "ZZZZZZ"]

可以通过制定闭包来为数组指定一个新的排序方式

names.sorted {
 $0.characters.count > $1.characters.count}
// ["ZZZZZZ", "EEEEE", "CCCC", "BB", "A"]

用闭包来遍历集合

集合实现了很多很实用的功能,比如排序,筛选等等。
这些功能都配合闭包实用,来定义不同的规则

var prices = [1.5, 10, 4.99, 2.30, 8.19]
let largePrices = prices.filter { 
          return $0 > 5
}

利用闭包配合map方法,可以将数组里面的每个值,进行指定的操作,并返回一个新的数组

let salePrices = prices.map {
 return $0 * 0.9}
let sum = prices.reduce(0) {
     return $0 + $1
}

同样的,也可以对字典进行对应的操作

let stock = [1.5:5, 10:2, 4.99:20, 2.30:5, 8.19:30]let stockSum = stock.reduce(0) {
 return $0 + $1.key * Double($1.value)}

字典遍历的时候返回的是一个元组,所以可以分别拿到key和value进行操作

举个例子来说明下上面几个集合的功能函数配合闭包的用法

let namesAndAges = ["Owen": 40, "Pogba": 21, "Martial": 21, "Rashford": 19, "Marry": 17, "Lina": 15]

// 使用reduce将数组中的名字连成一串

names.reduce(""){
    return $0 + $1
}

//将上面的数组,先筛选出所有名字超过4个字符的,然后将数组中的名字连成一串

let filteredNames = names.filter{
    return $0.characters.count > 4
}
filteredNames.reduce(""){
    $0 + $1
}
IOS
Web note ad 1