Swift 3.0之七、闭包

闭包能够捕获和存储其上下文中的常量和变量的引用,闭合并包裹这些常量和变量,因此被称为“闭包”。

函数是特殊形式的闭包,常见的闭包形式为:

  • 全局函数: 有名字但不会捕获任何值的闭包
  • 内嵌函数: 有名字且能从其上层函数捕获值的闭包
  • 闭包表达式: 是一个轻量级语法写的、可以捕获其上下文中常量或变量值的没有名字的闭包。(匿名函数)

1. 闭包表达式

Sorted 方法

Swift 的标准库提供了一个叫做sorted(by:)的方法为已知类型的数组元素进行排序,参数为“两个数组元素类型的形参和返回值为Bool类型”的闭包。一旦排序完成,sorted(by:)方法会返回与原数组类型大小相同、排序好的数组,原始数组不会被sorted(by:)方法修改。举个🌰 :

let names = ["Chris","Alex","Ewa","Barry","Daniella"]
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
// reversedNames 的值为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

闭包表达式语法

闭包表达式语法的一般形式:

 { (参数1: 类型,参数2: 类型,...) -> (返回类型) in
    // 要实现的内容
 }

举个🌰 ,将上面的栗子简化为闭包表达式写法就是:

reversedNames = names.sorted(by: 
{ (s1: String, s2: String) -> Bool in // in 关键字表示函数声明部分完成,后面即函数体。
    return s1 > s2
})

从语境中推断类型

sorted(by:)的参数为闭包,且Swift能推断它为(String, String) -> Bool类型。这意味着 (String, String)Bool可以省略,包括返回箭头(->)和形式参数名外面的括号也可被省略:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 })

从单表达式闭包隐式返回

单表达式闭包可以省略return关键字:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })

简写的实际参数名

Swift 自动对行内闭包提供简写实际参数名,可以通过$0$1$2等名字来引用闭包的实际参数值。
如果在闭包表达式中使用这些简写实际参数名,那么可以忽略实际参数列表的书写,in关键字也能被省略:

reversedNames = names.sorted(by: { $0 > $1 } )

运算符函数

由于String类型定义了大于号(>)的实现就是(String, String) -> Bool类型,因此:

reversedNames = names.sorted(by: >)

2. 尾随闭包

闭包表达式作为函数最后一个实际参数传递给函数时,可以写在小括号后面,如上面的栗子:

reversedNames = names.sorted(by: { $0 > $1 } )

采用尾随闭包的写法为:

reversedNames = names.sorted() { $0 > $1 }

如果闭包是唯一的参数,采用尾随闭包的写法时,可以省略括号,即:

reversedNames = names.sorted { $0 > $1 }

当闭包很长时,采用尾随闭包的写法显得非常美观。

3. 捕获值

一个闭包能够从上下文捕获已被定义的常量和变量,即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值,举一个内嵌函数的🌰 :

//  这是一个根据传入数值返回“增加器”函数的栗子
// 其中,内嵌函数incrementer()对外部的runningTotal变量进行了修改
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

接下来,使用这个函数:

// 首先,创建一个“加10器”函数
let incrementByTen = makeIncrementer(forIncrement: 10)
// 调用这个函数
incrementByTen()
// 返回 10
incrementByTen()
// 返回 20
incrementByTen()
// 返回 30

// 创建另外一个“加7器”函数
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回 7

// 再次调用“加10器”函数
incremByTen()
// 返回 40

4. 闭包是引用类型

在上面例子中, incrementBySevenincrementByTen 是常量,但是这些常量指向的闭包仍可以增加已捕获的变量 runningTotal 的值,这是因为函数和闭包都是引用类型。
这意味着赋值一个闭包到两个不同的常量或变量中,这两个常量或变量都指向相同的闭包:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回 50

5. 逃逸闭包

在函数return之前调用闭包称之为不可逃逸闭包,用@noescape关键字来明确闭包不允许逃逸。
在函数return之后再调用闭包称之为可逃逸闭包,用@escaping关键字来明确闭包允许逃逸。
(不加关键字的情况,默认执行的是非逃逸闭包)。
举个逃逸闭包的🌰 :

// 首先,创建一个数组,数组内的元素都是“() -> Void”类型的函数
var completionHandlers: [() -> Void] = []
// 这个函数用到逃逸闭包 用来给上面的数组添加函数
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler) 
} // 如果不使用@escaping关键字,会遇到编译时错误。
// 这个函数为非逃逸闭包
func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
 
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 } // 逃逸闭包在访问变量时,要显式地使用self关键字。
        someFunctionWithNonescapingClosure { x = 200 }   // 非逃逸闭包在访问变量时,可以省略self关键字。
    }
}
 
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 结果为: "200"
 
completionHandlers.first?()
print(instance.x)
// 结果为: "100"

6. 自动闭包

自动闭包有两个特点: 不接受任何形参,即() -> T类型; 不调用不执行。写法为:

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 结果为: "5"
 
let customerProvider = { customersInLine.removeAtIndex(0) } // 创建自动闭包并赋值给customerProvider常量,但闭包内的语句并未执行。
print(customersInLine.count)
// 结果为: "5"
 
print("Now serving \(customerProvider())!")  // 直到调用customerProvider()时,闭包内的语句真正执行。
// 结果为: "Now serving Chris!"
print(customersInLine.count)
// 结果为: "4"

通过@autoclosure关键字标记形式参数使用了自动闭包,这样,在调用时可以省略闭包外面的大括号,如:

// customersInLine 现在为 ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0)) 
// 因为上面使用了@autoclosure关键字,相应的参数会自动转换为闭包的形式传递,
// 所以这里调用时可以将“{ customersInLine.remove(at: 0) }”的大括号省略。
// 结果为: "Now serving Ewa!"

如果要自动闭包允许逃逸,就同时使用 @autoclosure@escaping 关键字:

// customersInLine 现在为 ["Barry", "Daniella"]
// 创建元素类型为“() -> String”的数组
var customerProviders: [() -> String] = []
// 这个函数的参数用到了自动闭包 并允许逃逸
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
 
print("Collected \(customerProviders.count) closures.")
// 结果为: "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 结果为: "Now serving Barry!"
// 结果为: "Now serving Daniella!"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容