- 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作,
- 闭包有三种形式:全局函数,嵌套函数,闭包表达式
1:全局函数是一个有名字但不会捕获任何值的闭包
2:嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包,嵌套函数是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在您处理一些函数并需要将另外一些函数作为该函数的参数时
3:闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包,闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了,闭包表达式语法可以使用常量、变量和inout类型作为参数,不能提供默认值。也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值
- 函数部分我们学的全局函数和嵌套函数就是特殊的闭包,详情可以看我的上一部分 swift-day04-函数,这里主要学习闭包表达式
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。闭包可以捕获和存储其所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作,
//闭包有三种形式:全局函数,嵌套函数,闭包表达式
//1:全局函数是一个有名字但不会捕获任何值的闭包
//2:嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包,嵌套函数是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在您处理一些函数并需要将另外一些函数作为该函数的参数时
//3:闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包,闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了,闭包表达式语法可以使用常量、变量和inout类型作为参数,不能提供默认值。也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值
//函数部分我们学的全局函数和嵌套函数就是特殊的闭包,详情可以看我的上一部分swiftday04-函数这里主要学习闭包表达式
//闭包表达式的一般形式:{ (parameters) -> returnType in statements}
//********************************闭包表达式*******************************************************
//首先了解一下sort方法:Swift 标准库提供了名为sort的方法,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,sort(_:)方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被sort(_:)方法修改。sort(_:)方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true,反之返回false
let aryys = ["a","e","d","f","s","c","h"];
func backwards(s1:String,s2:String) -> Bool{
return s1 > s2;
}
//如果第一个字符串(s1)大于第二个字符串(s2),backwards(_:_:)函数返回true,表示在新的数组中s1应该出现在s2前
print(aryys.sort(backwards))//["s", "h", "f", "e", "d", "c", "a"]
//如果采用闭包表达式
var reversed = aryys.sort({(s1:String,s2:String) -> Bool in
return s1 > s2;
})
//根据上下文推断类型(Inferring Type From Context)因为排序闭包函数是作为sort(_:)方法的参数传入的,Swift 可以推断其参数和返回值的类型。sort(_:)方法被一个字符串数组调用,因此其参数必须是(String, String) -> Bool类型的函数。这意味着(String, String)和Bool类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:
reversed = aryys.sort({s1,s2 in return s1>s2})
print(reversed)//["s", "h", "f", "e", "d", "c", "a"]
//单表达式闭包隐式返回,单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果
reversed = aryys.sort({s1,s2 in s1>s2})//sort(_:)方法的第二个参数函数类型明确了闭包必须返回一个Bool类型值。因为闭包函数体只包含了一个单一表达式(s1 > s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略。
//参数名称的缩写,Swift 自动为内联闭包提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数,以此类推。如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
reversed = aryys.sort({$0 > $1} )//,$0和$1表示闭包中第一个和第二个String类型的参数。
//运算符函数,Swift 的String类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。而这正好与sort(_:)方法的第二个参数需要的函数类型相符合。因此,您可以简单地传递一个大于号,Swift 可以自动推断出您想使用大于号的字符串函数实现:
//尾随闭包,如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用
func someFunction(mathFunction1: (Int, Int) -> Int){
print("-------\(mathFunction1(5,2))")
}
//不使用尾随闭包进行函数调用 someFunction({ 闭包主体部分})
someFunction({ (a: Int, b: Int) -> Int in
return a * b
})//在此调用打印结果为-------10
//使用尾随闭包进行函数调用someFunction(){闭包函数体部分}
someFunction(){
(a: Int, b: Int) -> Int in
return a + b
}//在此调用打印结果为-------7
//Swift 的Array类型有一个map(_:)方法,其获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定,当提供给数组的闭包应用于每个数组元素后,map(_:)方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值,下面举例说明
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbersArray = [5,16,249,7483];
let numberString = numbersArray.map { (var number) -> String in
var output = String()
while number > 0{
output = digitNames[number % 10]! + output;
number /= 10
}
return output;
}
//map(_:)为数组中每一个元素调用了闭包表达式。您不需要指定闭包的输入参数number的类型,因为可以通过要映射的数组类型进行推断。在该例中,闭包number参数被声明为一个变量参数,因此可以在闭包函数体内对其进行修改,而不用再定义一个新的局部变量并将number的值赋值给它。闭包表达式指定了返回类型为String,以表明存储映射值的新数组类型为String。闭包表达式在每次被调用的时候创建了一个叫做output的字符串并返回。其使用求余运算符(number % 10)计算最后一位数字并利用digitNames字典获取所映射的字符串,字典digitNames下标后跟着一个叹号(!),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定number % 10总是digitNames字典的有效下标,因此叹号可以用于强制解包 (force-unwrap) 存储在下标的可选类型的返回值中的String类型的值
print(numberString)//输出结果:["Five", "OneSix", "TwoFourNine", "SevenFourEightThree"]
//************************************捕获值****************************************************
//捕获值,闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量,看下面的例子
// totalNumber返回类型为() -> Int。这意味着其返回的是一个函数,而不是一个简单类型的值。该函数在每次调用时不接受参数,只返回一个Int类型的值。关于函数返回其他函数的内容,请查看函数类型作为返回类型。
//
// totalNumber(forIncrement:)函数定义了一个初始值为0的整型变量runningTotal,用来存储当前跑步总数。该值通过incrementor返回。
//
// totalNumber(forIncrement:)有一个Int类型的参数,其外部参数名为forIncrement,内部参数名为amount,该参数表示每次incrementor被调用时runningTotal将要增加的量。
func totalNumber(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let test1 = totalNumber(forIncrement: 20)
print( test1());//20
print( test1())//40
print( test1())//60
print( test1())//80
let test2 = totalNumber(forIncrement: 2)
print(test2())//2
print(test1())//100
//通过上面的打印值可以看出定义了一个叫做test1的常量,该常量指向一个每次调用会将runningTotal变量增加10的test1函数,如果您创建了另一个test2,它会有属于它自己的一个全新、独立的runningTotal变量的引用,再次调用原来的test1会在原来的变量runningTotal上继续增加值,该变量和test2中捕获的变量没有任何联系
//闭包是引用类型,test1和test2是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。无论您将函数或闭包赋值给一个常量还是变量,您实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用test1是一个常量,而并非闭包内容本身。这也意味着如果您将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包
//********************************自动闭包***********************************
//自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够用一个普通的表达式来代替显式的闭包,从而省略闭包的花括号。自动闭包让你能够延迟求值,因为代码段不会被执行直到你调用这个闭包。延迟求值对于那些有副作用和代价昂贵的代码来说是很有益处的,因为你能控制代码什么时候执行。下面的代码展示了闭包如何延时求值
var arry1 = ["p","l","hh","d","hkjk","e"];
print(arry1.count)//3
let arryProvider = {arry1.removeFirst()};
print(arry1.count)//3
print("Now remove \(arryProvider ())");//Now remove p
print(arry1.count)//2
//从打印结果可以看出:尽管在闭包的代码中,arry1的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,arryProvider的类型不是String,而是() -> String,一个没有参数且返回值为String的函数
//将闭包作为参数传递给函数时,你能获得同样的延时求值行为
func saveArry1(customerProvider: () -> String){
print("Now save \(customerProvider())")
}
saveArry1({arry1.removeFirst()})//Now save l
//saveArry1(_:)接受一个返回顾客名字的显式的闭包。下面这个版本的saveArry2(_:)完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为@autoclosure来接收一个自动闭包。现在你可以将该函数当做接受String类型参数的函数来调用。customerProvider参数将自动转化为一个闭包,因为该参数被标记了@autoclosure特性
func saveArry2(@autoclosure customerProvider: () -> String){
print("Now save \(customerProvider())")
}
saveArry2(arry1.removeFirst())//Now save hh
//如果想闭包调用前就进行了操作就采用@autoclosure(escaping)
var customerProviders: [() -> String] = []
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
customerProviders.append(customerProvider)
}
collectCustomerProviders(arry1.removeAtIndex(0))
collectCustomerProviders(arry1.removeAtIndex(0))
print("Collected \(customerProviders.count) closures.")
// prints "Collected 2 closures."
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
//输出结果Now serving d!
// Now serving hkjk!
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
网友评论