Swift3.0_闭包(Closure)

闭包基础

** 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似 **

如果要使用闭包,首先要闭包赋值给变量或者常量
定义一个可以持有闭包的变量

var multiplyClosure: (Int, Int) -> Int

将闭包赋值给前面定义的变量

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

使用定义好的闭包

let result = multiplyClosure(4, 2)
// result is 8

闭包函数的简易写法

  1. 如果闭包只声明了一个返回值,那么可以将return省略掉
multiplyClosure = { (a: Int, b: Int) -> Int in
        a*b
}
  1. 可以省略所有的类型信息,因为前面定义的变量中已经指明了闭包的参数类型和返回值类型
multiplyClosure = { (a, b) in
        a*b
}
  1. 省略掉所有参数列表。闭包中可以通过$加一个从0开始的数字来表示参数。
    不过这种写法个人认为可读性不强,不建议这么写
multiplyClosure = {
      $0 * $1
}

下面来通过一个例子来简单的使用一下这几种写法

首先定义一个函数,其中第三个参数是一个函数

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

接着调用这个函数

let addClosure = { (a: Int, b: Int) in   //定义一个闭包  
        a+b
}
operateOnNumbers(4, 2, operation: addClosure)

当然,operation这个参数,也可以直接传函数,传闭包是因为闭包也是没有名称的函数,对于operateOnNumbers来说,调用闭包或者函数对它来说没有区别

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

下面来看几种简易的写法

operateOnNumbers(3, 4, operation: {$0 * $1}) //上文的第三种简写

operateOnNumbers(3, 4, operation: {(a,b) in
      a * b   //第二种,省略类型的简写
})
operateOnNumbers(3, 4, operation: {
    (a: Int, b: Int) -> Int in //第一种省略return
    a * b
})
operateOnNumbers(3, 4, operation:  * ) //最简易的写法

尾随闭包

operateOnNumbers(3, 4){
    $0 * $1   // 这种简写比较特殊,只有当闭包是作为函数的最后一个参数的时候才可以这么用
}

定义无参数无返回值的闭包

let voidClosure: () -> Void = {
 print("Swift Apprentice is awesome!")}
voidClosure()

闭包--值捕获

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

swift中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量

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

incrementer闭包可以捕获makeIncrementer(forIncrement:)中的runningTotal和amount变量的引用,捕获引用保证了runningTotal和amount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal依旧存在。

let incrementByTen = makeIncrementor(forIncrement: 10)
incrementByTen()// 返回的值为10
incrementByTen()// 返回的值为20
incrementByTen()// 返回的值为30

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

incrementByTen()// 返回的值为40

可以看到incrementByTen中的变量和incrementBySeven中的变量没有任何联系,而且,他们分别保持着对变量的引用。

闭包是引用类型

** 函数和闭包都是引用类型 **

无论你讲函数或闭包赋值给一个常量还是变量,实际上都是讲常量或变量的值设置为对应的函数或闭包的引用。 上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。

如果将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:

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

逃逸闭包(Escaping Closures)

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,称闭包从函数中逃逸。
当定义接受闭包作为参数的函数时,可以在参数名之前标注@escaping,用来指明这个闭包是允许“逃逸”出这个函数的。

一种能使闭包逃逸出函数的方法是,将这个闭包保存在函数外部定义的变量中。
例如:很多启动异步操作的函数接受一个闭包参数作为completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包知道异步操作结束后才会被调用。
这种情况,闭包需要逃逸出函数,因为闭包需要在函数返回之后被调用。

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

如果不加@escaping关键字,将会得到一个编译错误

将一个闭包标记为@escaping意味着你必须在闭包中显示地引用self

var completionHandlers: [() -> Void] = []

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

func someFunctionWithNoneEscapingClosure(closure: () -> Void){
    closure()
}

class SomeClass{
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure {
            self.x = 100
        }
        someFunctionWithNoneEscapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x) //打印出200

completionHandlers.first?()
print(instance.x) //打印出100

自动闭包

自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。

自动闭包能够延迟求职,直到调用这个闭包,代码段才回被执行。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]print(customersInLine.count)
// 打印出 "5"
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"

只有当闭包customerProvider执行时,才会执行remove操作

利用闭包来自定义排序方法

数组的排序

let names = ["ZZZZZZ", "BB", "A", "CCCC", "EEEEE"]names.sorted()
// ["A", "BB", "CCCC", "EEEEE", "ZZZZZZ"]

可以通过制定闭包来为数组指定一个新的排序方式

names.sorted {
 $0.characters.count > $1.characters.count}
// ["ZZZZZZ", "EEEEE", "CCCC", "BB", "A"]

用闭包来遍历集合

集合实现了很多很实用的功能,比如排序,筛选等等。
这些功能都配合闭包实用,来定义不同的规则

var prices = [1.5, 10, 4.99, 2.30, 8.19]
let largePrices = prices.filter { 
          return $0 > 5
}

利用闭包配合map方法,可以将数组里面的每个值,进行指定的操作,并返回一个新的数组

let salePrices = prices.map {
 return $0 * 0.9}
let sum = prices.reduce(0) {
     return $0 + $1
}

同样的,也可以对字典进行对应的操作

let stock = [1.5:5, 10:2, 4.99:20, 2.30:5, 8.19:30]let stockSum = stock.reduce(0) {
 return $0 + $1.key * Double($1.value)}

字典遍历的时候返回的是一个元组,所以可以分别拿到key和value进行操作

举个例子来说明下上面几个集合的功能函数配合闭包的用法

let namesAndAges = ["Owen": 40, "Pogba": 21, "Martial": 21, "Rashford": 19, "Marry": 17, "Lina": 15]

// 使用reduce将数组中的名字连成一串

names.reduce(""){
    return $0 + $1
}

//将上面的数组,先筛选出所有名字超过4个字符的,然后将数组中的名字连成一串

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

推荐阅读更多精彩内容