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
捕获两个变量,runningTotal
和runningTotal
。通过捕获两个变量之后,每次调用函数makeIncrementer
,嵌套函数incrementer
都会让runningTotal
加runningTotal
,并以闭包的形式作为函数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)——闭包那些事儿!