闭包是自包括的函数代码块,能够在代码中被传递和使用。

Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其它一些编程语言中的 lambdas 函数比較类似。

 

闭包能够捕获和存储其所在上下文中随意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量。俗称闭包。Swift 会为您管理在捕获过程中涉及到的全部内存操作。

 

注意:

 

假设您不熟悉捕获(capturing)这个概念也不用操心。您能够在值捕获 章节对其进行详细了解。

在函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包。闭包採取例如以下三种形式之中的一个: 

全局函数是一个有名字但不会捕获不论什么值的闭包

嵌套函数是一个有名字并能够捕获其封闭函数域内值的闭包

闭包表达式是一个利用轻量级语法所写的能够捕获其上下文中变量或常量值的匿名闭包

Swift 的闭包表达式拥有简洁的风格。并鼓舞在常见场景中进行语法优化,主要优化例如以下:

 

利用上下文判断參数和返回值类型

隐式返回单表达式闭包,即单表达式闭包能够省略returnkeyword

參数名称缩写

跟随(Trailing)闭包语法

 

闭包表达式(Closure Expressions)

嵌套函数是一个在较复杂函数中方便进行命名和定义自包括代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是非常实用处的。尤其是在您处理一些函数并须要将另外一些函数作为该函数的參数时。

 

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

以下闭包表达式的样例通过使用几次迭代展示了sort函数定义和语法优化的方式。

每一次迭代都用更简洁的方式描写叙述了同样的功能。

 

 

sort 函数(The Sort Function)

Swift 标准库提供了sort函数,会依据您提供的基于输出类型排序的闭包函数将已知类型数组中的值进行排序。一旦排序完毕,函数会返回一个与原数组大小同样的新数组,该数组中包括已经正确排序的同类型元素。

 

以下的闭包表达式演示样例使用sort函数对一个String类型的数组进行字母逆序排序,以下是初始数组值:

 

let names = ["Chris","Alex", "Ewa", "Barry", "Daniella"]

sort函数须要传入两个參数:

 

已知类型的数组

闭包函数,该闭包函数须要传入与数组类型同样的两个值。并返回一个布尔类型值来告诉sort函数当排序结束后传入的第一个參数排在第二个參数前面还是后面。假设第一个參数值出如今第二个參数值前面。排序闭包函数须要返回true。反之返回false。

该样例对一个String类型的数组进行排序,因此排序闭包函数类型需为(String, String) -> Bool。

 

提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为sort函数的第二个參数传入:

 

func backwards(s1: String, s2: String)-> Bool {
return s1 > s2
}
var reversed = sort(names, backwards)
// reversed 为 ["Ewa","Daniella", "Chris", "Barry", "Alex"]

假设第一个字符串 (s1) 大于第二个字符串 (s2),backwards函数返回true。表示在新的数组中s1应该出如今s2前。

对于字符串中的字符来说。“大于”表示 “依照字母顺序较晚出现”。 这意味着字母"B"大于字母"A",字符串"Tom"大于字符串"Tim"。

其将进行字母逆序排序,"Barry"将会排在"Alex"之后。

 

然而,这是一个相当冗长的方式,本质上仅仅是写了一个单表达式函数 (a > b)。

在以下的样例中。利用闭合表达式语法能够更好的构造一个内联排序闭包。

 

 

闭包表达式语法(Closure Expression Syntax)

闭包表达式语法有例如以下一般形式:

 

{ (parameters) -> returnType in
statements
}

闭包表达式语法能够使用常量、变量和inout类型作为參数。不提供默认值。也能够在參数列表的最后使用可变參数。

元组也能够作为參数和返回值。

 

以下的样例展示了之前backwards函数相应的闭包表达式版本号的代码:

 

reversed = sort(names, { (s1: String, s2:String) -> Bool in
return s1 > s2
})

须要注意的是内联闭包參数和返回值类型声明与backwards函数类型声明同样。

在这两种方式中。都写成了(s1: String, s2: String) -> Bool。 然而在内联闭包表达式中。函数和返回值类型都写在大括号内,而不是大括号外。

 

闭包的函数体部分由keywordin引入。该keyword表示闭包的參数和返回值类型定义已经完毕,闭包函数体即将開始。

 

由于这个闭包的函数体部分如此短以至于能够将其改写成一行代码:

 

reversed = sort(names, { (s1: String, s2:String) -> Bool in return s1 > s2 } )

这说明sort函数的总体调用保持不变。一对圆括号仍然包裹住了函数中整个參数集合。而当中一个參数如今变成了内联闭包(相比于backwards版本号的代码)。

 

 

依据上下文判断类型(Inferring Type From Context)

由于排序闭包函数是作为sort函数的參数进行传入的,Swift能够判断其參数和返回值的类型。 sort期望第二个參数是类型为(String, String) -> Bool的函数。因此实际上String,String和Bool类型并不须要作为闭包表达式定义中的一部分。

由于全部的类型都能够被正确判断,返回箭头 (->) 和环绕在參数周围的括号也能够被省略:

 

reversed = sort(names, { s1, s2 in returns1 > s2 } )

实际上不论什么情况下。通过内联闭包表达式构造的闭包作为參数传递给函数时,都能够判断出闭包的參数和返回值类型。这意味着您差点儿不须要利用完整格式构造不论什么内联闭包。

 

 

单表达式闭包隐式返回(Implicit Return From Single-Expression Clossures)

单行表达式闭包能够通过隐藏returnkeyword来隐式返回单行表达式的结果,如上版本号的样例能够改写为:

 

reversed = sort(names, { s1, s2 in s1 >s2 } )

在这个样例中,sort函数的第二个參数函数类型明白了闭包必须返回一个Bool类型值。由于闭包函数体仅仅包括了一个单一表达式 (s1 > s2),该表达式返回Bool类型值,因此这里没有歧义,returnkeyword能够省略。

 

 

參数名称缩写(Shorthand Argument Names)

Swift 自己主动为内联函数提供了參数名称缩写功能,您能够直接通过$0,$1,$2来顺序调用闭包的參数。

 

假设您在闭包表达式中使用參数名称缩写,您能够在闭包參数列表中省略对其的定义,而且相应參数名称缩写的类型会通过函数类型进行判断。 inkeyword也同样能够被省略,由于此时闭包表达式全然由闭包函数体构成:

 

reversed = sort(names, { $0 > $1 } )

在这个样例中。$0和$1表示闭包中第一个和第二个String类型的參数。

 

 

运算符函数(Operator Functions)

实际上另一种更简短的方式来撰写上面样例中的闭包表达式。 Swift 的String类型定义了关于大于号 (>) 的字符串实现。其作为一个函数接受两个String类型的參数并返回Bool类型的值。

而这正好与sort函数的第二个參数须要的函数类型相符合。

因此,您能够简单地传递一个大于号,Swift能够自己主动判断出您想使用大于号的字符串函数实现:

 

reversed = sort(names, >)

很多其它关于运算符表达式的内容请查看运算符函数。

 

 

跟随闭包(Trailing Closures)

假设您须要将一个非常长的闭包表达式作为最后一个參数传递给函数。能够使用跟随闭包来增强函数的可读性。跟随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个參数调用。

 

func someFunctionThatTakesAClosure(closure:() -> ()) {
// 函数体部分
} // 以下是不使用跟随闭包进行函数调用 someFunctionThatTakesAClosure({
// 闭包主体部分
}) // 以下是使用跟随闭包进行函数调用 someFunctionThatTakesAClosure() {
// 闭包主体部分
}

注意:

 

假设函数仅仅须要闭包表达式一个參数,当您使用跟随闭包时,您甚至能够把()省略掉。

在上例中作为sort函数參数的字符串排序闭包能够改写为:

 

reversed = sort(names) { $0 > $1 }

当闭包非常长以至于不能在一行中进行书写时,跟随闭包变得非常实用。

举例来说。Swift 的Array类型有一个map方法,其获取一个闭包表达式作为其唯一參数。

数组中的每个元素调用一次该闭包函数,并返回该元素所映射的值(也能够是不同类型的值)。详细的映射方式和返回值类型由闭包来指定。

 

当提供给数组闭包函数后,map方法将返回一个新的数组,数组中包括了与原数组一一相应的映射后的值。

 

下例介绍了怎样在map方法中使用跟随闭包将Int类型数组[16,58,510]转换为包括相应String类型的数组["OneSix", "FiveEight","FiveOneZero"]:

 

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]

如上代码创建了一个数字位和他们名字映射的英文版本号字典。同一时候定义了一个准备转换为字符串的整型数组。

 

您如今能够通过传递一个跟随闭包给numbers的map方法来创建相应的字符串版本号数组。须要注意的时调用numbers.map不须要在map后面包括不论什么括号,由于其仅仅须要传递闭包表达式这一个參数,而且该闭包表达式參数通过跟随方式进行撰写:

 

let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
// strings 常量被判断为字符串类型数组,即String[]
// 其值为["OneSix", "FiveEight", "FiveOneZero"]

map在数组中为每个元素调用了闭包表达式。

您不须要指定闭包的输入參数number的类型,由于能够通过要映射的数组类型进行判断。

 

闭包number參数被声明为一个变量參数(变量的详细描写叙述请參看常量參数和变量參数),因此能够在闭包函数体内对其进行改动。

闭包表达式制定了返回类型为String。以表明存储映射值的新数组类型为String。

 

闭包表达式在每次被调用的时候创建了一个字符串并返回。其使用求余运算符 (number % 10) 计算最后一位数字并利用digitNames字典获取所映射的字符串。

 

注意:

 

字典digitNames下标后跟着一个叹号 (!),由于字典下标返回一个可选值 (optional value),表明即使该 key 不存在也不会查找失败。在上例中。它保证了number % 10能够总是作为一个digitNames字典的有效下标 key。因此叹号能够用于强制解析 (force-unwrap) 存储在可选下标项中的String类型值。

从digitNames字典中获取的字符串被加入到输出的前部,逆序建立了一个字符串版本号的数字。

(在表达式number % 10中,假设number为16。则返回6。58返回8。510返回0)。

 

number变量之后除以10。 由于其是整数,在计算过程中未除尽部分被忽略。 因此 16变成了1,58变成了5,510变成了51。

 

整个过程反复进行,直到number /= 10为0,这时闭包会将字符串输出。而map函数则会将字符串加入到所映射的数组中。

 

上例中跟随闭包语法在函数后整洁封装了详细的闭包功能,而不再须要将整个闭包包裹在map函数的括号内。

 

 

捕获值(Capturing Values)

闭包能够在其定义的上下文中捕获常量或变量。

即使定义这些常量和变量的原域已经不存在。闭包仍然能够在闭包函数体内引用和改动这些值。

 

Swift最简单的闭包形式是嵌套函数,也就是定义在其它函数的函数体内的函数。

嵌套函数能够捕获其外部函数全部的參数以及定义的常量和变量。

 

下例为一个叫做makeIncrementor的函数,其包括了一个叫做incrementor嵌套函数。嵌套函数incrementor从上下文中捕获了两个值。runningTotal和amount。之后makeIncrementor将incrementor作为闭包返回。

每次调用incrementor时。其会以amount作为增量添加runningTotal的值。

 

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

makeIncrementor返回类型为() ->Int。 这意味着其返回的是一个函数,而不是一个简单类型值。 该函数在每次调用时不接受參数仅仅返回一个Int类型的值。关于函数返回其它函数的内容,请查看函数类型作为返回类型。

 

makeIncrementor函数定义了一个整型变量runningTotal(初始为0) 用来存储当前跑步总数。

该值通过incrementor返回。

 

makeIncrementor有一个Int类型的參数。其外部命名为forIncrement, 内部命名为amount,表示每次incrementor被调用时runningTotal将要添加的量。

 

incrementor函数用来运行实际的添加操作。

该函数简单地使runningTotal添加amount。并将其返回。

 

假设我们单独看这个函数。会发现看上去不同平常:

 

func incrementor() -> Int {
runningTotal += amount
return runningTotal
}

incrementor函数并没有获取不论什么參数,可是在函数体内訪问了runningTotal和amount变量。这是由于其通过捕获在包括它的函数体内已经存在的runningTotal和amount变量而实现。

 

由于没有改动amount变量,incrementor实际上捕获并存储了该变量的一个副本。而该副本随着incrementor一同被存储。

 

然而。由于每次调用该函数的时候都会改动runningTotal的值,incrementor捕获了当前runningTotal变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当makeIncrementor结束时候并不会消失,也保证了当下一次运行incrementor函数时,runningTotal能够继续添加。

 

注意:

 

Swift 会决定捕获引用还是拷贝值。 您不须要标注amount或者runningTotal来声明在嵌入的incrementor函数中的使用方式。Swift 同一时候也处理runingTotal变量的内存管理操作。假设不再被incrementor函数使用。则会被清除。

以下代码为一个使用makeIncrementor的样例:

 

let incrementByTen =makeIncrementor(forIncrement: 10)

该样例定义了一个叫做incrementByTen的常量,该常量指向一个每次调用会加10的incrementor函数。调用这个函数多次能够得到以下结果:

 

incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30

假设您创建了另一个incrementor。其会有一个属于自己的独立的runningTotal变量的引用。以下的样例中。incrementBySevne捕获了一个新的runningTotal变量,该变量和incrementByTen中捕获的变量没有不论什么联系:

 

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

注意:

 

假设您闭包分配给一个类实例的属性。而且该闭包通过指向该实例或其成员来捕获了该实例。您将创建一个在闭包和实例间的强引用环。 Swift 使用捕获列表来打破这样的强引用环。很多其它信息。请參考闭包引起的循环强引用。

 

闭包是引用类型(Closures Are Reference Types)

上面的样例中,incrementBySeven和incrementByTen是常量,可是这些常量指向的闭包仍然能够添加其捕获的变量值。

这是由于函数和闭包都是引用类型。

 

不管您将函数/闭包赋值给一个常量还是变量。您实际上都是将常量/变量的值设置为相应函数/闭包的引用。

上面的样例中,incrementByTen指向闭包的引用是一个常量,而并不是闭包内容本身。

 

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

 

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

Swift编程语言学习6—— 闭包的更多相关文章

  1. Swift编程语言学习9—— 存储属性和计算属性

    属性将值跟特定的类.结构或枚举关联.存储属性存储常量或变量作为实例的一部分,计算属性计算(而不是存储)一个值.计算属性能够用于类.结构体和枚举里,存储属性仅仅能用于类和结构体. 存储属性和计算属性通经 ...

  2. Swift 编程语言学习0.1——Swift简单介绍

    有的时候,认为看英文文档有些费时,看中文文档怕翻译不准,有些地方确实不须要抠字眼.当有些地方假设翻译不精准会产生歧义,所以用这样对比的方式.顺便学习一下Swift. Swift is a new pr ...

  3. swift开发学习笔记-闭包

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/jiangqq781931404/article/details/32913421 文章转自:http ...

  4. Swift编程语言学习11—— 枚举全局变量、局部变量与类型属性

    全局变量和局部变量 计算属性和属性监视器所描写叙述的模式也能够用于全局变量和局部变量,全局变量是在函数.方法.闭包或不论什么类型之外定义的变量,局部变量是在函数.方法或闭包内部定义的变量. 前面章节提 ...

  5. Swift编程语言学习4.3—— 控制语句

    控制传递语句(Control Transfer Statements) 控制转移语句改变你代码的运行顺序,通过它你能够实现代码的跳转.Swift有四种控制转移语句. continue break fa ...

  6. Swift编程语言学习2.1——基础运营商(在)

    操作员正在检查,更改.归并值特殊符号或短语.例如,加+这两个数字相加(例如let i = 1 + 2). 算如更复杂的逻辑和操作的实施&&(例如if enteredDoorCode & ...

  7. Swift编程语言学习1.4——数值型字面量、数值类型转换

    数值型字面量 整数字面量能够被写作: 一个十进制数,没有前缀 一个二进制数,前缀是0b 一个八进制数,前缀是0o 一个十六进制数,前缀是0x 以下的全部整数字面量的十进制值都是17: let deci ...

  8. Swift编程语言学习3.1排列

    Swift 语言提供经典的数组和字典两种集合类型来存储集合数据.数组用来按顺序存储同样类型的数据.字典尽管无序存储同样类型数据值可是须要由独有的标识符引用和寻址(就是键值对). Swift 语言里的数 ...

  9. Swift编程语言学习1.3——类型安全和投机型

    Swift 是类型安全(type safe )语言.类型安全的语言可以让你清楚地知道代码被处理值类型.假设你需要一个代码String.你绝对不能进去一个不小心传球Int. 因为 Swift 它是类型安 ...

随机推荐

  1. 【Excle数据透视表】如何水平并排显示报表筛选区域的字段

    原始效果 目标效果 解决方案 设置数据透视表"在报表区域筛选显示字段"为"水平并排" 步骤 方法① 单击数据透视表任意单元格→数据透视表工具→分析→选项→布局和 ...

  2. Hbase 目录树

    转自 http://www.cnblogs.com/nexiyi/p/hbase_on_hdfs_directory.html 总所周知,HBase 是天生就是架设在 HDFS 上,在这个分布式文件系 ...

  3. Maven - error in opening zip file

    在一个maven工程中,有时执行mvn打包,部署,编译等命令,例如mvn clean install -DskipTests -U等命令时,会报类似(error in opening zip file ...

  4. Burp Suite基本用法

    从上一篇已经知道Burp Suite安装.启动方法,本章将会阐述Burp Suite抓包.重放.爆破.双参数爆破.爬虫等基本用法.同博客园看到一篇描述Burp Suite界面各个字段和按钮作用,感兴趣 ...

  5. PHP框架认识初步

    PHP框架比較 CodeIgniter Codeigniter 相当轻量级.下载下来就能用, CI 的最大特点就是可扩展性非常强 你能够通过不改动源代码的方式 优雅的扩展差点儿全部的东西. think ...

  6. 【优才原创】Android的拖放机制

    优才网 [优才原创]Android的拖放机制 2016-04-18 优才学院 优才网 一.拖放机制概述 ² 拖放操作是手指触摸屏幕上的某一对象.然后拖动该对象.最后在屏幕的某个位置释放该对象并运行某种 ...

  7. mysql时间操作(时间差和时间戳和时间字符串的互转)

    mysql时间操作(时间差和时间戳和时间字符串的互转) 两个时间差: MySQL datediff(date1,date2):两个日期相减 date1 - date2,返回天数. select dat ...

  8. Atitit.rust语言特性 attilax 总结

    Atitit.rust语言特性 attilax 总结 1. 创建这个新语言的目的是为了解决一个顽疾:软件的演进速度大大低于硬件的演进,软件在语言级别上无法真正利用多核计算带来的性能提升.1 2. 不会 ...

  9. inotify+rsync

    backup_to_rsync.sh #!/bin/bash #source function library . /etc/init.d/functions rsync_host=rsync.eti ...

  10. Unity3d 发动机原理详细介绍

    Unity3d 发动机原理详细介绍 www.MyException.Cn   发布于:2013-10-08 16:32:36   浏览:46次 0     Unity3d 引擎原理详细介绍 体系结构 ...