Swift笔记——闭包

Posted 柯梵KFAaron

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift笔记——闭包相关的知识,希望对你有一定的参考价值。

每次看闭包的时候,都觉得挺简单的。不就是Objective-C中的block嘛,但是长时间不看,再来用的时候有些地方老是印象模糊,特别是一些省略写法。好记性不如烂笔头,还是决定整理整理记下来。

闭包描述

排序方法

swift中有个排序的方法sorted(by:),它会根据你提供的闭包,对数组已知类型的元素进行排序。原数组:

let nameArr = ["zhangsan", "lisi", "wangwu", "zhujiu"]

因为这个数组的元素类型是字符串,所以需要传的闭包类型是(String, String) -> Bool写个这样类型的函数,并编译,就得出了排序之后的新数组了。

let nameArr = ["zhangsan", "lisi", "wangwu", "zhujiu"]
func nameSortFunction(_ n1: String, _ n2: String) -> Bool {
    return n1 > n2
}
let newNameArr = nameArr.sorted(by: nameSortFunction)
print(nameArr)
print(newNameArr)
// ["zhangsan", "lisi", "wangwu", "zhujiu"]
// ["zhujiu", "zhangsan", "wangwu", "lisi"]
闭包语法

闭包的一般格式:

 { (parameters) -> return type in
    statements
}

上面的例子可以闭包的形式替代:

let newNameArr = nameArr.sorted(by: {(n1: String, n2: String) -> Bool in
    return n1 > n2
})

闭包的内容是以in开始的,in标志着闭包的参数和返参已经定义完成,准备开始功能实现了。因为这个闭包的内容很少,所以甚至可以写成一行。

let newNameArr = nameArr.sorted(by: {(n1: String, n2: String) -> Bool in return n1 > n2 })
类型推断

因为闭包是作为一个参数传入一个方法,所以swift可以推断它参数的类型。上面的例子中,对一个元素为字符串的数组排序,所以传入的闭包类型必须为(String, String) -> Bool,这就意味着(String, String)Bool类型不用写。因为所有的类型都可以推断,所以箭头和圆括号也可以删掉。当然如果继续写类型的话,在swift中是鼓励的,这样可以方便代码的读者阅读。

let newNameArr = nameArr.sorted(by: {n1, n2 in return n1 > n2 })
单行表达式的含蓄返回

如果闭包只有一句表达式,那么它会自动加上返回的功能,所以return可以省略。

let newNameArr = nameArr.sorted(by: {n1, n2 in n1 > n2 })
速写参数名

swift会自动对内联闭包提供速写参数名,比如$0$1$2等等。如果用这种方式的话,参数列表和in也可以删掉。

let newNameArr = nameArr.sorted(by: { $0 > $1 })
算子

上面这个例子甚至还有一种更简单的方式,就是在闭包中直接传入一个大于的算子,swift可以推断你想要它针对字符串特定的实现。

let newNameArr = nameArr.sorted(by: > )

尾随闭包

如果闭包是作为一个函数的最后一个参数传入,并且这个闭包很长,那么可以使用尾随闭包替代。尾随闭包是写在函数的调用括号外面,虽然它还是函数的一个参数。上面的例子就可以使用尾随闭包。
使用前:

let newNameArr = nameArr.sorted(by: { $0 > $1 })

使用后:

let newNameArr = nameArr.sorted(){ $0 > $1 }

如果闭包是这个函数或方法的唯一参数,而且又是一个尾随闭包的话,那么连括号都可以删掉。

let newNameArr = nameArr.sorted{ $0 > $1 }

复杂一点的例子。用map(_:)把一个Int类型的数组转化成String类型的数组。

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

方法:

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
print(strings)
// ["OneSix", "FiveEight", "FiveOneZero"]

在这个闭包里,不用明确每个参数的类型是Int,因为闭包会自动推断。

捕捉变量

闭包可以捕获周围上下文中的常量和变量。现在有一个嵌套闭包。函数makeIncrementer包含一个嵌套函数incrementer。嵌套函数incrementer捕获两个变量,runningTotalrunningTotal。通过捕获两个变量之后,每次调用函数makeIncrementer,嵌套函数incrementer都会让runningTotalrunningTotal,并以闭包的形式作为函数makeIncrementer的返参返回。

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

定义一个变量接收函数makeIncrementer返回的闭包,并连续调用。

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

在函数makeIncrementer里定义了一个变量runningTotal用来储存累计增加的总数,初始化为0。调用makeIncrementer返回来一个() -> Int类型的闭包。第一次调incrementByTen时,runningTotal为0,amount为10,调用后,返回10(0+10),同时runningTotal变为10,以此类推。如果再调用一次函数makeIncrementer生成一个新的闭包,并调用该闭包的话,那么该闭包捕获的runningTotal的初始值仍然为0,这时候再重新调用incrementByTen,它的runningTotal仍然为30,不受新生成的闭包的影响。

let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementBySeven())
// 7
print(incrementByTen())
// 40

闭包是指针类型

定义一个常量指向闭包时,只是定义一个指向闭包的常量指针,而不是闭包本身是常量。比如上例中,如果定义另一个常量指向incrementByTen,它其实指向的是同一个闭包。

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

逃逸闭包

当一个闭包作为一个参数传入一个函数,并且这个闭包是在这个参数执行返回之后调用,那么这个闭包就是一个逃逸闭包,需要在参数类型前面用@escaping字段修饰,标明这个闭包可以逃逸,否则会报编译错误。

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

使用@escaping标记闭包,意味着你在闭包内部必须明确指向self

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

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

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

completionHandlers.first?()
print(instance.x)
// 100

自动闭包

自动闭包用@autoclosure修饰,使用自动闭包可以延缓及时性要求不那么高的闭包的编译。但是使用自动闭包会增加代码的阅读难度。

Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.

备注

Xcode版本:9.3.1
Swift版本:4.1
官方文档:Closures


以上是关于Swift笔记——闭包的主要内容,如果未能解决你的问题,请参考以下文章

《从零开始学Swift》学习笔记(Day 22)——闭包那些事儿!

Swift学习笔记——闭包的几种形式

Swift学习笔记:闭包

《从零开始学Swift》学习笔记(Day 23)——尾随闭包

swift学习笔记2——函数闭包

Swift笔记——闭包