一个下午让你掌握Swift基础 ( 8/9 ) 闭包

简介

这是一个 Swift 语言教程,基于最新的 iOS 9,Xcode 7.3 和 Swift 2.2,会为你介绍 Swift 编程非常基础的内容。从电脑如何工作的全程基本原理到语言结构,你会足够了解这门语言,来处理数据和管理代码的行为。

快速链接


闭包

之前那篇文章全是关于函数的。但 Swift 有另一个对象,你可以使用它将代码分解成可重复使用的块,它叫做闭包(closure)

还记得你从简单明了的值比如 10 和 “你好”开始,然后通过变量和常量给他们命名?闭包和函数很像 — 除了你在开始学习函数的时候,他们都有名字。

一个闭包是一个简单的函数但没有名字。你可以把他们赋值给变量然后传递他们,就像任意其他值一样。声明它们不需要创建一个完全形式的函数,这让他们使用、传递都更方便,就像你在这篇文章里即将看到的。

闭包基础

闭包叫这个名字是因为他们有能力把变量和常量“关闭”在闭包自己的 scope 里。这只是意味着,如果一个闭包想要从包围的上下文中访问、存储和操作任意的变量或常量的值,它就可以。一个闭包体内的变量和常量被认为被闭包捕获(capture)了。

你可能想问,“如果闭包是没有名字的函数,那你怎么使用它们?”要使用一个闭包,你首先必须要把它赋值给一个变量或常量。在这种方式中,闭包是一个类型,如任何其他类型。

这是一个可以保存一个闭包的变量声明:

var multiplyClosure: (Int, Int) -> Int

multiplyClosure 带有两个 Int 值并且返回一个 Int。注意这和为函数声明一个变量完全相同。就像我说的,一个闭包是没有名字的函数!

赋值一个闭包给一个变量,像这样:

multiplyClosure = { (a: Int, b: Int) -> Int in
    return a * b
}

这看起来就像函数声明,但有一点细微的变化。有相同的参数列表,->符号和返回类型。但对于闭包,这些出现在花括号里,并且有一个 in 关键字在返回类型的后面。

闭包变量定义了后,你可以使用它就像它是一个函数一样,例如:

let result = multiplyClosure(4, 2)

就像你想的,result 等于 8。

简写语法

相比函数,闭包被设计的很轻量。有很多方式来缩短语法。首先,你可以去掉闭包里的 return,像这样:

multiplyClosure = { (a: Int, b: Int) -> Int in
    a * b
}

如果闭包只有一行表达式,你可以移除返回语句,Swift 会为你推断。

你还可以使用 Swift 的类型推断来缩短语法,通过移除返回类型:

multiplyClosure = { (a: Int, b: Int) in
    a * b
}

这里唯一的区别是你移除掉了 -> Int。记住,你已经声明 multiplyClosure 为一个返回一个 Int 的闭包了,所以你可以让 Swift 为你推断这部分的类型。

类型判断允许我们更短一些。声明闭包的时候参数的类型可以被移除,像这样:

multiplyClosure = { (a, b) in
    a * b
}

最后,如果你想你甚至可以忽略参数列表。Swift 允许你通过数字指向每个参数,从零开始,像这样:

multiplyClosure = {
    $0 * $1
}

参数列表,返回类型和 in 关键字全部没了,你的新闭包声明比原始版本远远要短了。像这样的数字化参数真的应该只被用在短的、可爱的闭包里,就像这一个。如果太长了,要记忆每个数字化参数指向的是什么就会很混淆,那你就应该用命名语法。

现在让我们看看这样可以变得多么有用。

考虑如下代码:

func operateOnNumbers(a: Int, _ b: Int,
                  operation: (Int, Int) -> Int) -> Int {
    let result = operation(a, b)
    print(result)
    return result
}

这声明了一个叫做 operateOnNumbers 的函数,带有前两个 Int 值参数。第三个参数叫做 operation,显示为一个函数类型。operateOnNumbers 自己返回一个 Int。

你然后可以结合一个闭包来使用 operateOnNumbers ,像这样:

let addClosure = { (a: Int, b: Int) in
    a+b 
}
operateOnNumbers(4, 2, operation: addClosure)

记住,闭包只是没有名字的函数。你也可以传递一个函数进去,作为 operateOnNumbers 的第三个参数,所以学习到这点的时候就不值得惊讶,像这样:

func addFunction(a: Int, b: Int) -> Int {
    return a + b
}
operateOnNumbers(4, 2, operation: addFunction)

operateOnNumbers 被用相同的方式调用,不论 operation 是一个函数还是一个闭包。

但是,这里闭包语法的强力又再一次来到手边了。你可以在调用 operateOnNumbers 函数的行内定义闭包,像这样:

operateOnNumbers(4, 2, operation: { (a: Int, b: Int) -> Int in
    return a + b
})

不需要定义闭包,然后把它赋值给一个本地变量或常量;你可以简单地声明闭包,就在把它作为一个参数传递给函数的地方!

但回顾一下你可以简化语法来移除很多样板代码。因此你可以把上面的减少成下面这样:

operateOnNumbers(4, 2, operation: {
    $0 + $1
})

还有一种方式可以简化语法,但只有在闭包是传递给函数的最后一个参数的时候才可以。这个例子里,你可以把闭包移到函数调用外面:

operateOnNumbers(4, 2) {
    $0 + $1
}

这看起来可能很奇怪,但这就和前一个代码段相同,除了你移除掉了 operation 标签,并且把大括号拉到了函数调用参数列表的外面。这叫做尾随闭包语法(trailing closure syntax)

没有返回值的闭包

直到现在,你看到的闭包都带有一个或更多参数,并且有返回值。但就像函数,闭包没有被要求一定要做这些事情。这是你如何声明一个闭包,不带有参数并且不返回东西:

let voidClosure: () -> Void = {
    print("几个高级和弦 几个切分音 我写的 Swift 代码也可以很有音乐性!")}
voidClosure()

这个闭包的类型是 () -> Void。空的括号表示没有参数。你必须声明一个返回类型,所以 Swift 才知道你在声明一个闭包。这是 Void 出现在手边的地方,它就是它的名字表示的意思:闭包什么都不返回。

从围绕 scope 中捕获

最终,让我们回到定义一个闭包的特征:它可以访问自己的 scope 里的变量和常量。

**注意:**回顾一下,scope 定义了在哪里一个实体(变量,常量,等等)是可以访问的。介绍 if 语句的时候你看到了一个新的 scope。闭包也介绍了一个新的 scope,并且继承了所有在它定义的地方可见的实体到 scope 里。

例如,采用下面的闭包:

var counter = 0
let incrementCounter = {
    counter += 1
}

incrementCounter 非常简单:它递增了 counter 变量。counter 变量定义在闭包外面。闭包能够访问变量的原因是它被定义在和变量相同的 scope 里。闭包被称作捕获(capture)了 counter 变量。它对于变量做的任何改变在闭包里和外面都是可见的。

假设你调用了5次闭包,像这样:

incrementCounter()
incrementCounter()
incrementCounter()
incrementCounter()
incrementCounter()

这5次调用之后,counter 会等于5。

闭包可以被用来从围绕的 scope 捕获变量的事实会变得非常有用。例如,你可以写这个函数:

func countingClosure() -> (() -> Int) {
    var counter = 0
    let incrementCounter: () -> Int = {
        counter += 1
        return counter
    }
    return incrementCounter
}

函数不带有参数,返回一个闭包。它返回的闭包自己不带有参数并且返回一个 Int。

这个函数每次被调用的时候会返回的闭包会递增它的内置 counter(计数器)。每次你调用这个函数会获得一个不同的 counter。

例如,这个可以被这样使用:

let counter1 = countingClosure()
let counter2 = countingClosure()
counter1() // 0
counter2() // 0
counter1() // 1
counter1() // 2
counter2() // 1

函数创建的两个 counter 互相独立,单独技术。干净!

这就是闭包啦!

关键点

  • 闭包(closures)是没有名字的函数。他们可以被赋值给变量并且作为参数传递给函数。
  • 闭包有简写语法(shorthand syntax),让他们比使用其他函数简单许多。
  • 一个闭包可以从它的包围上下文中捕获(capture)变量和常量。

接下来去哪儿?

闭包和函数是把你的代码存储为可复用代码块的基本类型。除了声明和调用他们,你也看到了它们在作为参数传递给其它函数和闭包的时候有多么方便。

你会在接下来的文章里返回处理数据,你会学习可选值和集合类型。还有,保持闭包和函数在脑海里,你会看到如何把每个东西组合—变量、常量和函数—在一起,用到你自己定义的类型里,从后面的“结构体”文章开始。

在此期间,看看下面的挑战来测试你的知识,然后再继续。

挑战

挑战 A:重复你自己

你的第一个挑战是写一个函数,会运行一个给定的闭包指定次数。

像这样声明函数:

func repeatTask(times: Int, task: () -> Void)

函数应该运行 task 闭包,times 次。
使用这个函数 print “一个下午掌握 Swift 基础!” 10 次。

挑战 B:闭包总和

这个挑战里,你要写一个函数,你可以复用来创建不同的数学和。

像这样声明函数:

func mathSum(times: Int, operation: (Int) -> Int) -> Int

第一个参数,times,定义了重复的次数。第二个参数,operation,是一个闭包,带有一个 Int 并且返回一个 Int。

operation 应该有一个参数是当前重复的次数,并且以此次重复结果的数字作为返回值。

mathSum 应该重复 times 次,从 1 开始。它应该保持每次重复的 operation 结果的和。

使用函数找到前 10 个平方数的和,等于 385。然后用函数找到前 10 个斐波那契数列数字的和,等于 143。对于斐波那契数列,你可以之前在前一章挑战里写的函数—或者找一个现成的如果你没有做的话!

挑战源代码

https://yunpan.cn/cBWszdN7nkNjr (提取码:aaa4)


介绍

欢迎来到Swift世界!Swift是一门苹果在2014年夏天发布的编程语言。从那之后,Swift发布了一个主要的版本跳跃,成为了开始在苹果平台:iOS,OS X,watchOS和tvOS开发的最简单的方式。

谁适合这篇教程

这篇教程适合懂一点编程、并且希望学习Swift的人。也许你已经为网站写过一些JavaScript代码,或者用Python写过一些简短的程序。这篇教程就是为你准备的!你会学习到编程的基本概念,同时也会成为Swift语言小能手。

如果你是赤裸裸的编程新手,这篇教程也是为你准备的!教程里贯穿有简短的锻炼和挑战来给你一些编程练习,同时测试你的知识。

需要准备什么

要看这篇教程,你需要准备如下的东西:

  • 一台运行OS X El Captian(10.11)的Mac,带有最新发布的更新并且安装了安全补丁。这样你才能够安装需要的开发工具:最新版本的Xcode。
  • Xcode 7.3 或更新的版本。Xcode是用Swift写代码的主要开发工具。最小也需要Xcode 7.3版本,因为那个版本包含Swift 2.2。你可以免费从Mac App Store下载Xcode的最新版本,这里:http://apple.co/1FLn51R

如果你还没有安装Xcode最新版本,在继续看下面的教程前要确定安装。

如何使用这篇教程

每篇教程都会介绍触手可及的话题理论,伴随大量Swift代码来示范在学习的实际的应用程序。

教程里的所有代码都是平台中立的;这意味着不是为iOS、OS X或任何其它平台而特定。代码在playgrounds里运行,你在本篇中已经学习了。

在剩下的教程里,你可以把代码在自己的playground里输入进去。这样你就可以和代码“玩耍”(play around),做一些改变立即就能看见代码运行的结果。

剩下的教程里会贯穿实际小练习,都是简短的练习,关于触手可及的主题。每篇的末尾也有挑战,会有编程问题也会有长一点的代码练习来测试你的知识。做完就能掌握大部分的Swift基础知识。

教程更新

教程会随Swift语言的更新而更新,会发布在我的简书和知乎专栏上,搜索“张嘉夫”即可关注我。

目录

本教程从一些基础工作开始来让你起步:

  • 第1篇,编程本质 & Playground基础 - 这就是,到编程世界的入门介绍!你会从电脑和编程的预览开始,然后剩余时间给Swift playground打个招呼。
  • 第2篇,变量 & 常量 - 你会学习到变量和常量,这是用来存储数据的“地方”。你也会学习数据类型以及Swift如何追踪数据类型并在代码中进行传输。
  • 第3篇,数字类型 & 操作 - 你会从基础的数字类型比如整形和浮点型数字开始,当然也包括布尔类型。也会看到一些在实际操作,从比较到算数操作如加减。
  • 第4篇,字符串 - 用字符串来存储文字-从按钮里的文字到图片的标注到这篇教程全部的文字,存储这所有的一切!你会学习到string和character类型,以及基于这些类型的一些常见操作。

在脑海中有基础数据类型后,就该用那些数据做一些事情了:

  • 第5篇,做判断 - 代码不总是直接从头运行到尾。你会学习在代码里如何做判决并且设定情况来运行某段代码。
  • 第6篇,重复步骤 - 继续不要让代码直线运行的主题,你会学习到如何使用循环来重复某些步骤。
  • 第7篇,函数 - 函数是Swift中用来构建代码的基础建筑。你会学习到如何定义函数来分组代码到可复用单元中。
  • 第8篇,闭包(Closures) - 闭包和函数很接近。你会学习到如何使用它们来轻松传递代码块。

最后一篇会回到数据:

  • 第9节,可选值 - 这篇讲可选值,Swift中的一种特殊类型,表示既有可能是一个真实的值也有可能没有值。这篇的最后你会知道为什么要用可选值以及如何安全地使用它们。

这些基础会让你快速开始Swift之路,做好接触更高级编程主题的准备。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容