Swift教程之闭包详解

闭包(Closures)是独立的函数代码块,能在代码中传递及使用。Swift中的闭包与C和Objective-C中的代码块及其它编程语言中的匿名函数相似。

闭包可以在上下文的范围内捕获、存储任何被定义的常量和变量引用。因这些常量和变量的封闭性,而命名为“闭包(Closures)”。Swift能够对所有你所能捕获到的引用进行内存管理。

NOTE

假如你对“捕获(capturing)”不熟悉,请不要担心,具体可以参考Capturing Values(捕获值)。

全局函数和嵌套函数已在 Functions(函数)中介绍过,实际上这些都是特殊的闭包函数
全局函数都是闭包,特点是有函数名但没有捕获任何值。
嵌套函数都是闭包,特点是有函数名,并且可以在它封闭的函数中捕获值。
闭包表达式都是闭包,特点是没有函数名,可以使用轻量的语法在它所围绕的上下文中捕获值。
Swift的闭包表达式有着干净,清晰的风格,并常见情况下对于鼓励简短、整洁的语法做出优化。这些优化包括:
推理参数及返回值类型源自上下文
隐式返回源于单一表达式闭包
简约参数名
尾随闭包语法

1、闭包表达式

嵌套函数已经在Nested Functions(嵌套函数)中有所介绍,是种方便命名和定义自包含代码块的一种方式,然而,有时候在编写简短函数式的构造器时非常有用,它不需要完整的函数声明及函数名,尤其是在你需要调用一个或多个参数的函数时。
闭包表达式是一种编写内联闭包的方式,它简洁、紧凑。闭包表达式提供了数种语义优化,为的是以最简单的形式编程而不需要大量的声明或意图。以下以同一个sort函数进行几次改进,每次函数都更加简洁,以此说明闭包表达式的优化。

Sort函数

Swift的标准函数库提供了一个名为sort的函数,它通过基于输出类型排序的闭包函数,给已知类型的数组数据的值排序。一旦完成排序工作,会返回一个同先前数组相同大小,相同数据类型,并且的新数组,并且这个数组的元素都在正确排好序的位置上。
The closure expression examples below use the sort function to sort an array of String values in reverse alphabetical order. Here's the initial array to be sorted:
以下的闭包表达式通过sort函数将String值按字母顺序进行排序作说明,这是待排序的初始化数组。

代码如下:

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

sort函数需要两个参数:

一个已知值类型的数组
一个接收两个参数的闭包函数,这两个参数的数据类型都同于数组元素。并且
返回一个Bool表明是否第一个参数应排在第二个参数前或后。

这个例子是一组排序的字符串值,因此需要排序的封闭类型的函数(字符串,字符串)-> Bool。

构造排序闭包的一种方式是书写一个符合其类型要求的普通函数:backwards,并将其返回值作为 sort 函数的第二个参数传入:

代码如下:

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

如果backwards函数参数 s1 大于 s2,则返回true值,表示在新的数组排序中 s1 应该出现在 s2 前。 字符中的 “大于” 表示 “按照字母顺序后出现”。 这意味着字母 “B” 大于字母 “A”, 字符串 “Tom” 大于字符串 “Tim”。 其将进行字母逆序排序,”Barry” 将会排在 “Alex” 之后,以此类推。

但这是一个相当冗长的方式,本质上只是做了一个简单的单表达式函数 :(a > b)。 下面的例子中,我们利用闭合表达式可以相比上面的例子更效率的构造一个内联排序闭包。

闭包表达式语法

闭合表达式语法具有以下一般构造形式:

代码如下:

{ (parameters) -> return type in
statements
}

闭包表达式语法可以使用常量参数、变量参数和 inout 类型作为参数,但皆不可提供默认值。 如果你需要使用一个可变的参数,可将可变参数放在最后,元组类型也可以作为参数和返回值使用。

下面的例子展示了上面的 backwards 函数对应的闭包表达式构造函数代码

代码如下:

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

需要注意的是声明内联闭包的参数和返回值类型与 backwards 函数类型声明相同。 在这两种方式中,都写成了 (s1: String, s2: String) -> Bool类型。 然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。

闭包的函数体部分由关键字 in 引入。 该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。

因为这个闭包的函数体非常简约短所以完全可以将上面的backwards函数缩写成一行连贯的代码

代码如下:

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

可以看出 sort 函数的整体调用保持不变,还是一对圆括号包含两个参数变成了内联闭包形式、只不过第二个参数的值变成了。而其中一个参数现在变成了内联闭包 (相比于 backwards 版本的代码)。

根据上下文推断类型

因为排序闭包是作为函数的参数进行传入的,Swift可以推断其参数和返回值的类型。 sort 期望第二个参数是类型为 (String, String) -> Bool 的函数,因此实际上 String, String 和 Bool 类型并不需要作为闭包表达式定义中的一部分。 因为所有的类型都可以被正确推断,返回箭头 (->) 和 围绕在参数周围的括号也可以被省略:

代码如下:

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

实际情况下,通过构造内联闭包表达式的闭包作为函数的参数传递给函数时,都可以判断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。

同样,如果你希望避免阅读函数时可能存在的歧义, 你可以直接明确参数的类型。

这个排序函数例子,闭包的目的是很明确的,即排序被替换,而且对读者来说可以安全的假设闭包可能会使用字符串值,因为它正协助一个字符串数组进行排序。

单行表达式闭包可以省略 return

单行表达式闭包可以通过隐藏 return 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

代码如下:

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

在这个例子中,sort 函数的第二个参数函数类型明确了闭包必须返回一个 Bool 类型值。 因为闭包函数体只包含了一个单一表达式 (s1 > s2),该表达式返回 Bool 类型值,因此这里没有歧义,return关键字可以省略。

参数名简写

Swift 自动为内联函数提供了参数名称简写功能,可以直接通过 $0,$1,$2等名字来引用闭包的参数值。

如果在闭包表达式中使用参数名称简写,可以在闭包参数列表中省略对其的定义,并且对应参数名称简写的类型会通过函数类型进行推断。 in 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

代码如下:

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

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

运算符函数
运算符函数实际上是一个更短的方式构造以上的表达式。

代码如下:

reversed = sort(names, >)

更多关于运算符表达式的内容请查看Operator Functions

2、Trailing 闭包

如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用 trailing 闭包来增强函数的可读性。

Trailing 闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用。

代码如下:

func someFunctionThatTakesAClosure(closure: () -> ()) {
// function body goes here
}
// here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure({
// closure's body goes here
})
// here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}

注意

如果函数只需要闭包表达式一个参数,当您使用 trailing 闭包时,您甚至可以把 () 省略掉。

在上例中作为 sort 函数参数的字符串排序闭包可以改写为:

代码如下:

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

当闭包非常长以至于不能在一行中进行书写时,Trailing 闭包就变得非常有用。 举例来说,Swift 的 Array 类型有一个 map 方法,其获取一个闭包表达式作为其唯一参数。数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。 具体的映射方式和返回值类型由闭包来指定。

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

下例介绍了如何在 map 方法中使用 trailing 闭包将 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]

上面的代码创建了整数数字到他们的英文名字之间映射字典。 同时定义了一个准备转换为字符串的整型数组。

你现在可以通过传递一个 trailing 闭包给 numbers 的 map 方法来创建对应的字符串版本数组。需要注意的时调用 numbers.map不需要在 map 后面包含任何括号,因为只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过 trailing 方式进行撰写:

代码如下:

let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

map 在数组中为每一个元素调用了闭包表达式。您不需要指定闭包的输入参数 number 的类型,因为可以通过要映射的数组类型进行推断。

闭包 number 参数被声明为一个变量参数 (变量的具体描述请参看Constant and Variable Parameters),因此可以在闭包函数体内对其进行修改。闭包表达式制定了返回值类型为 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函数则会将字符串添加到所映射的数组中。

上例中 trailing 闭包语法在函数后整洁封装了具体的闭包功能,而不再需要将整个闭包包裹在 map 函数的括号内。

3、获取值

闭包可以在其定义的范围内捕捉(引用/得到)常量和变量,闭包可以引用和修改这些值,即使定义的常量和变量已经不复存在了依然可以修改和引用。牛逼吧、

在Swift中最简单形式是一个嵌套函数,写在另一个函数的方法里面。嵌套函数可以捕获任何外部函数的参数,也可以捕获任何常量和变量在外部函数的定义。

看下面这个例子,一个函数方法为makeIncrementor、这是一个嵌套函数,在这个函数体内嵌套了另一个函数方法:incrementor,在这个incrementor函数体内有两个参数: runningTotal和amount,实际运作时传进所需的两个参数后,incrementor函数每次被调用时都会返回一个runningTotal值提供给外部的makeIncrementor使用:

代码如下:

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

而函数makeincrementor的返回类型值我们可以通过函数名后面的()-> int得知返回的是一个Int类型的值。如需想学习了解更多地函数返回类型,可以参考: Function Types as Return Types.(超链接跳转)

我们可以看见makeincrementor这个函数体内首先定义了一个整型变量:runningtotal,初始值为 0 ,而incrementor()函数最终运行的出来的返回值会赋值给这个整型变量。

makeincrementor函数()中向外部抛出了一个forIncrement参数供外部穿参进来、一旦有值进入函数体内会被函数实例化替代为amount,而amount会被传递进内嵌的incrementor函数体中与整型常量runningTotal相加得到一个新的runningTotal并返回。而我们这个主函数要返回的值是Int类型,runningTotal直接作为最终值被返回出去、makeincrementor函数()执行完毕。

makeincrementor函数()在其内部又定义了一个新的函数体incrementor,作用就是将外部传递过来的值amount 传进incrementor函数中与整形常量runningTotal相加得到一个新的runningTotal,

单独的看incrementor函数、你会发现这个函数不寻常:

代码如下:

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

因为incrementor函数没有任何的参数,但是在它的函数方法体内却指向runningTotal和amount,显而易见、这是incrementor函数获取了外部函数的值amount,incrementor不能去修改它但是却可以和体内的runningTotal相加得出新的runningTotal值返回出去。

不过,由于runningtotal每次被调用时都会相加改变一次实际值,相应地incrementor函数被调用时会去加载最新的runningtotal值,而不再是第一次初始化的0.并且需要保证每次runningTotal的值在makeIncrementor函数体内不会丢失直到函数完全加载完毕。要能确保在函数体内下一次引用时上一次的值依然还在。

注意
Swift中需要明确知道什么时候该引用什么时候该赋值,在incrementor函数中你不需要注解amount 和runningTotal。Swift还负责处理当函数不在需要runningTotal的时候,内存应该如何去管理。

这里有一个例子makeIncrementor函数:

代码如下:

let incrementByTen = makeIncrementor(forIncrement: 10)

4、引用类型闭包

在上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量在闭包的状态下依然可以被修改。为何?很简单,因为函数和闭包是引用类型。
当你指定一个函数或一个闭包常量/变量时、实际上是在设置该常量或变量是否为一个引用函数。在上面的例子中,它是闭合的选择,incrementByTen指的是恒定的,而不是封闭件本身的内容。
这也意味着,如果你分配一个封闭两种不同的常量或变量,这两个常量或变量将引用同一个闭包:

代码如下:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// returns a value of 50

(0)

相关推荐

  • Swift 中闭包的简单使用

    本文主要是介绍Swift中闭包的简单使用,将从"闭包的定义"."闭包的创建.赋值.调用"."闭包常见的几种使用场景","使用闭包可能引起的循环强引用" 四个方面入手,重点介绍闭包如何使用,没有高深的概念,只是专注于实际使用,属于入门级水平,后面还会有关于闭包更加详细和深入理解的文章.希望大家在阅读完本文后能够对闭包有一个整体的理解以及能够简单的使用它. 闭包的定义 在Swift开发文档中是这样介绍闭包的:闭包是可以在你的代码中

  • 详解Swift中的函数及函数闭包使用

    一.引言 函数是有特定功能的代码段,函数会有一个特定的名称调用时来使用.Swift提供了十分灵活的方式来创建与调用函数.事实上在Swift,每个函数都是一种类型,这种类型由参数和返回值来决定.Swift和Objective-C的一大区别就在于Swift中的函数可以进行嵌套. 而Swift中的闭包是有一定功能的代码块,这十分类似于Objective-C中的block语法.Swift中的闭包语法风格十分简洁,其作用和函数的作用相似. 二.函数的创建与调用 函数通过函数名,参数和返回值来定义,参数和返

  • Swift中闭包实战案例详解

    前言 无论苹果的官方文档还是由官方文档衍生出来的一些文章和书籍都比较重视基础语法知识的讲解,对于实战中的应用提及的都很少,所以当我们想使用"闭包"解决一些问题的时候,会忽然出现看着一堆理论知识却不知从何下手的尴尬感,这就是理论和时实战的区别了. 本文不赘述Swift闭包的的基本语法了,百度或者Google下有很多资料.如题所示本文着重讲述Swift闭包的一些实战案例,有需要的小伙伴可以参考下,经验丰富的大神也请指教. 关于如何理解闭包 学习闭包的第一个难点就是理解闭包,可能很多人用了很

  • Swift 3.0基础学习之闭包

    前言 闭包是功能性自包含模块,可以在代码中被传递和使用. Swift 中的闭包与 C 和 Objective-C中的 blocks 以及其他一些编程语言中的 lambdas 比较相似.下面这篇文章就来详细介绍了关于Swift 3.0中的闭包,感兴趣的一起来看看吧. 开始 闭包的书写格式如下: { (parameters) -> return type in statements } 如 reversedNames = names.sorted(by: { (s1: String, s2: Str

  • IOS swift3.0 下闭包语法整理

    IOS swift3.0 下闭包语法整理 一.闭包的概念 有oc基础的都知道,闭包其实是oc里面的block,语法格式不一样,但作用是一样的.主要是用于callBack(异步回调)或者两个类之间的通信.它的本质一个函数,一个可执行的代码块,只是这个函数是没有名字的,也就是匿名函数.你也可以把他看作如 int.float一样,是一种数据类型,一种可以作为参数传递的数据类型. 二.基本语法 1.闭包的声明 //定义一个求和闭包 //闭包类型:(Int,Int)->(Int) let add:(Int

  • 深入理解Swift语言中的闭包机制

    在 Swift 中的闭包类似于结构块,并可以在任何地方调用,它就像 C 和 Objective C 语言内置的函数. 函数内部定义的常数和变量引用可被捕获并存储在闭包.函数被视为封闭的特殊情况,它有 3 种形式. 在 Swift 语言闭合表达式,如下优化,重量轻语法风格,其中包括: 推导参数并从上下文菜单返回值的类型 从单封表达的隐性返回 简略参数名称 尾部闭包语法 语法 下面是一个通用的语法定义用于闭包,它接受参数并返回数据的类型: 复制代码 代码如下: {(parameters) -> re

  • Swift教程之闭包详解

    闭包(Closures)是独立的函数代码块,能在代码中传递及使用.Swift中的闭包与C和Objective-C中的代码块及其它编程语言中的匿名函数相似. 闭包可以在上下文的范围内捕获.存储任何被定义的常量和变量引用.因这些常量和变量的封闭性,而命名为"闭包(Closures)".Swift能够对所有你所能捕获到的引用进行内存管理. NOTE 假如你对"捕获(capturing)"不熟悉,请不要担心,具体可以参考Capturing Values(捕获值). 全局函数

  • Swift教程之属性详解

    属性是描述特定类.结构或者枚举的值.存储属性作为实例的一部分存储常量与变量的值,而计算属性计算他们的值(不只是存储).计算属性存在于类.结构与枚举中.存储属性仅仅只在类与结构中. 属性通常与特定类型实例联系在一起.但属性也可以与类型本身联系在一起,这样的属性称之为类型属性. 另外,可以定义属性观察者来处理属性值发生改变的情况,这样你就可以对用户操作做出反应.属性观察者可以被加在自己定义的存储属性之上,也可以在从父类继承的子类属性之上. 1.存储属性 最简单的情形,作为特定类或结构实例的一部分,存

  • Swift教程之下标详解

    类,结构和枚举类型都可以通过定义下标来访问一组或者一个序列中的成员元素.通过下标索引就可以方便地检索和设置相应的值,而不需要其他的额外操作.比如你可以通过someArray[index]来访问数组中的元素,或者someDictionary[key]来对字典进行索引. 你可以为一个类型定义多个下标,以及适当的下标重载用来根据传递给下标的索引来设置相应的值.下标不仅可以定义为一维的,还可以根据需要定义为多维的,多个参数的. 1.下标语法 下标可以让你通过实例名后加中括号内一个或多个数值的形式检索一个

  • Swift教程之控制流详解

    Swift提供了所有C语言中相似的控制流结构.包括for和while循环:if和switch条件语句:break和continue跳转语句等. Swift还加入了for-in循环语句,让编程人员可以在遍历数组,字典,范围,字符串或者其它序列时更加便捷. 相对于C语言,Swift中switch语句的case语句后,不会自动跳转到下一个语句,这样就避免了C语言中因为忘记break而造成的错误.另外case语句可以匹配多种类型,包括数据范围,元组,或者特定的类型等.switch语句中已匹配的数值也可以

  • Swift教程之继承详解

    一个类可以从另外一个类中继承方法,属性或者其它的一些特性.当一个类继承于另外一个类时,这个继承的类叫子类,被继承的类叫父类.继承是Swift中类区别于其它类型的一个基本特征. Swift中的类可以调用父类的方法,使用父类的属性和下标,还可以根据需要使用重写方法或者属性来重新定义和修改他们的一些特性.Swift可以帮助你检查重写的方法和父类的方法定义是相符的. 类还可以为它继承的属性添加观察者,这样可以能够让它在一个属性变化的时候得到通知.属性观察者可以被添加给任何属性,不管它之前是存储属性还是计

  • Swift教程之函数详解

    函数是执行特定任务的代码自包含块.给定一个函数名称标识, 当执行其任务时就可以用这个标识来进行"调用". Swift的统一的功能语法足够灵活来表达任何东西,无论是甚至没有参数名称的简单的C风格的函数表达式,还是需要为每个本地参数和外部参数设置复杂名称的Objective-C语言风格的函数.参数提供默认值,以简化函数调用,并通过设置在输入输出参数,在函数执行完成时修改传递的变量. Swift中的每个函数都有一个类型,包括函数的参数类型和返回类型.您可以方便的使用此类型像任何其他类型一样,

  • Swift教程之方法详解

    方法是关联到一个特定类型的函数.类.结构.枚举所有可以定义实例方法,封装特定任务和功能处理给定类型的一个实例.类.结构.枚举类型还可以定义方法,相关的类型本身.类型方法类似于objective – c类方法. 结构和枚举可以定义方法swift与C和objective – C是一个重大的区别.在objective – c中,类是唯一类型可以定义方法.在swift,你可以选择是否要定义一个类,结构,或枚举,还有你定义方法类型的灵活性创造. 1.实例方法 实例方法是属于一个特定的类,结构或枚举实例的功

  • IOS 开发之Swift 元组的实例详解

    IOS 开发之Swift 元组的实例详解 元组是多个值组合而成的复合值.元组中的值可以是任意类型,而且每一个元素的类型可以是不同的. 元组声明 //普通声明 var point = (5,2) var httpResponse = (404, "Not Found") //定义类型声明 var point2 : (Int,Int,Int) = (10,5,2) var httpResponse2 : (Int,String) = (200,"ok") 元组解包 va

  • python中函数总结之装饰器闭包详解

    1.前言 函数也是一个对象,从而可以增加属性,使用句点来表示属性. 如果内部函数的定义包含了在外部函数中定义的对象的引用(外部对象可以是在外部函数之外),那么内部函数被称之为闭包. 2.装饰器 装饰器就是包装原来的函数,从而在不需要修改原来代码的基础之上,可以做更多的事情. 装饰器语法如下: @deco2 @deco1 def func(arg1,arg2...): pass 这个表示了有两个装饰器的函数,那么表示的含义为:func = deco2(deco1(func)) 无参装饰器语法如下:

  • javascript 闭包详解及简单实例应用

    JS 闭包详解及实例: 最近学习JS的基础知识,学习了闭包的知识点,有很多疑惑,这一段时间还是一直有在看闭包的相关知识理解就更深入了一点,下面说说我的理解. function fn(){ var a = 0; return function (){ return ++a; } } 如上所示,上面第一个return返回的就是一个闭包,那么本质上说闭包就是一个函数.那么返回这个函数有什么用呢? 那是因为这个函数可以调用到它外部的a这个变量.其实也就是说,return返回的是这个函数 + a这个变量

随机推荐