Swift笔记——闭包

每次看闭包的时候,都觉得挺简单的。不就是Objective-C中的block嘛,但是长时间不看,再来用的时候有些地方老是印象模糊,特别是一些省略写法。好记性不如烂笔头,还是决定整理整理记下来。

闭包描述

排序方法

swift中有个排序的方法sorted(by:),它会根据你提供的闭包,对数组已知类型的元素进行排序。原数组:

let nameArr = ["zhangsan", "lisi", "wangwu", "zhujiu"]

因为这个数组的元素类型是字符串,所以需要传的闭包类型是(String, String) -> Bool写个这样类型的函数,并编译,就得出了排序之后的新数组了。

let nameArr = ["zhangsan", "lisi", "wangwu", "zhujiu"]
func nameSortFunction(_ n1: String, _ n2: String) -> Bool {
    return n1 > n2
}
let newNameArr = nameArr.sorted(by: nameSortFunction)
print(nameArr)
print(newNameArr)
// ["zhangsan", "lisi", "wangwu", "zhujiu"]
// ["zhujiu", "zhangsan", "wangwu", "lisi"]
闭包语法

闭包的一般格式:

 { (parameters) -> return type in
    statements
}

上面的例子可以闭包的形式替代:

let newNameArr = nameArr.sorted(by: {(n1: String, n2: String) -> Bool in
    return n1 > n2
})

闭包的内容是以in开始的,in标志着闭包的参数和返参已经定义完成,准备开始功能实现了。因为这个闭包的内容很少,所以甚至可以写成一行。

let newNameArr = nameArr.sorted(by: {(n1: String, n2: String) -> Bool in return n1 > n2 })
类型推断

因为闭包是作为一个参数传入一个方法,所以swift可以推断它参数的类型。上面的例子中,对一个元素为字符串的数组排序,所以传入的闭包类型必须为(String, String) -> Bool,这就意味着(String, String)Bool类型不用写。因为所有的类型都可以推断,所以箭头和圆括号也可以删掉。当然如果继续写类型的话,在swift中是鼓励的,这样可以方便代码的读者阅读。

let newNameArr = nameArr.sorted(by: {n1, n2 in return n1 > n2 })
单行表达式的含蓄返回

如果闭包只有一句表达式,那么它会自动加上返回的功能,所以return可以省略。

let newNameArr = nameArr.sorted(by: {n1, n2 in n1 > n2 })
速写参数名

swift会自动对内联闭包提供速写参数名,比如$0$1$2等等。如果用这种方式的话,参数列表和in也可以删掉。

let newNameArr = nameArr.sorted(by: { $0 > $1 })
算子

上面这个例子甚至还有一种更简单的方式,就是在闭包中直接传入一个大于的算子,swift可以推断你想要它针对字符串特定的实现。

let newNameArr = nameArr.sorted(by: > )

尾随闭包

如果闭包是作为一个函数的最后一个参数传入,并且这个闭包很长,那么可以使用尾随闭包替代。尾随闭包是写在函数的调用括号外面,虽然它还是函数的一个参数。上面的例子就可以使用尾随闭包。
使用前:

let newNameArr = nameArr.sorted(by: { $0 > $1 })

使用后:

let newNameArr = nameArr.sorted(){ $0 > $1 }

如果闭包是这个函数或方法的唯一参数,而且又是一个尾随闭包的话,那么连括号都可以删掉。

let newNameArr = nameArr.sorted{ $0 > $1 }

复杂一点的例子。用map(_:)把一个Int类型的数组转化成String类型的数组。

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]

方法:

let strings = numbers.map { (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
print(strings)
// ["OneSix", "FiveEight", "FiveOneZero"]

在这个闭包里,不用明确每个参数的类型是Int,因为闭包会自动推断。

捕捉变量

闭包可以捕获周围上下文中的常量和变量。现在有一个嵌套闭包。函数makeIncrementer包含一个嵌套函数incrementer。嵌套函数incrementer捕获两个变量,runningTotalrunningTotal。通过捕获两个变量之后,每次调用函数makeIncrementer,嵌套函数incrementer都会让runningTotalrunningTotal,并以闭包的形式作为函数makeIncrementer的返参返回。

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

定义一个变量接收函数makeIncrementer返回的闭包,并连续调用。

let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen())
// 10
print(incrementByTen())
// 20
print(incrementByTen())
// 30

在函数makeIncrementer里定义了一个变量runningTotal用来储存累计增加的总数,初始化为0。调用makeIncrementer返回来一个() -> Int类型的闭包。第一次调incrementByTen时,runningTotal为0,amount为10,调用后,返回10(0+10),同时runningTotal变为10,以此类推。如果再调用一次函数makeIncrementer生成一个新的闭包,并调用该闭包的话,那么该闭包捕获的runningTotal的初始值仍然为0,这时候再重新调用incrementByTen,它的runningTotal仍然为30,不受新生成的闭包的影响。

let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementBySeven())
// 7
print(incrementByTen())
// 40

闭包是指针类型

定义一个常量指向闭包时,只是定义一个指向闭包的常量指针,而不是闭包本身是常量。比如上例中,如果定义另一个常量指向incrementByTen,它其实指向的是同一个闭包。

let alsoIncrementByTen = incrementByTen
print(alsoIncrementByTen())
// 50

逃逸闭包

当一个闭包作为一个参数传入一个函数,并且这个闭包是在这个参数执行返回之后调用,那么这个闭包就是一个逃逸闭包,需要在参数类型前面用@escaping字段修饰,标明这个闭包可以逃逸,否则会报编译错误。

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

使用@escaping标记闭包,意味着你在闭包内部必须明确指向self

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
        
class SomeClass {
    var x = 10
     func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}
        
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 200
        
completionHandlers.first?()
print(instance.x)
// 100

自动闭包

自动闭包用@autoclosure修饰,使用自动闭包可以延缓及时性要求不那么高的闭包的编译。但是使用自动闭包会增加代码的阅读难度。

Overusing autoclosures can make your code hard to understand. The context and function name should make it clear that evaluation is being deferred.

备注

Xcode版本:9.3.1
Swift版本:4.1
官方文档:Closures

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

推荐阅读更多精彩内容