Swift 中的闭包

闭包和闭包表达式

  1. 闭包是可以在你的代码中杯传递和引用的功能性独立代码块
  2. 闭包能够捕获和储存定义再起上下文中任何常量和变量的引用,这也就是所谓的闭合并包裹哪些常量和变量,因此被称为“闭包”,Swift 能够为你处理所有关于捕获的内存管理的操作
  • 闭包的概念
  • 在函数中的全局和内嵌函数,实际上是特殊的闭包,闭包符合如下三种行驶中的一种:
  1. 全局函数是一个有名字,但不会捕获任何值的闭包
  2. 内嵌函数时一个有名字,且能从其上层函数捕获值的闭包
  3. 闭包表达式是一个轻量级语法所写的,可以捕获其上下文中常量或变量值的没有名字的闭包
  • 闭包表达式
  1. 闭包表达式是一种在简短行内就能写完的闭包的语法
  • 闭包表达式-从sorted 函数说起
  • Swift 的标准库提供了一种叫做 sorted(by:) 的方法,会根据你提供的排序闭包将已知类型的数组的值进行排序,一旦它排序完成, sorted(by:) 方法会返回与原数组类型大小完全相同的一个新数组,该数组的元素是已排序好的,原始数组不会被sorted(by:) 方法修改
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward(_:_:))
print(reversedNames)
  • 闭包表达式语法
  • 闭包表达式语法能够使用常量形式参数、变量形式参数和输入输出形式参数,但不能提供默认值。可变形式参数也能使用,但需要在形式参数列表的最后面使用。元组也可被用来作为形式参数和返回类型
{ (parameters) -> (return type) in
    statements
}
  • 闭包表达式语法版本的 backward
  1. 将之前 backward(::) 函数改为闭包表达版本
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted { (s1: String, s2: String) -> Bool in
    return s1 > s2
}
print(reversedNames)
  • 从语境中推断类型
  1. 因排序闭包为实际参数来传递给函数,故Swift能推断它的形式参数类型和返回类型
  2. sorted(by:) 方法期望它的形式参数是一个 (String, String) -> Bool 类型的函数,这意味着 (String, String) 和 Bool 类型不需要被写成闭包表达式定义中的一部分,因为所有的类型都能被推断,返回箭头 (->) 和围绕在形式参数名周围的括号也能被省略
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { s1, s2 in return s1 > s2})
print(reversedNames)

  • 从单表达式闭包隐式返回
  1. 单表达式闭包能够从他们的声明中删除return 关键字,来隐式返回他们单个表达式的结果
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { s1, s2 in s1 > s2})
print(reversedNames)
  • 简写实际参数名
  1. Swift自动对行内闭包提供简写实际参数,可以通过 0,1, $2 等名字来引用闭包的实际参数值
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: { $0 > $1})
print(reversedNames)
  • 运算符函数
    Swift 的 String类型定义了关于大于号(>)的特定字符串实现,让其作为一个由两个 String 类型形式参数的函数并返回一个 Bool 类型的值,这正好与sorted(by:) 方法的形式参数需要的函数相匹配,因此,你能简单的传递一个大于号,并且Swift 将推断你想使用大于号 特殊字符串函数实现
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted(by: >)
print(reversedNames)
  • 尾随闭包
  1. 如果你需要讲一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾随闭包将增强函数的可读性,尾随闭包是一个被书写在函数形式参数的括号外边(后面)的闭包表达式
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var reversedNames = names.sorted{ $0 > $1}
print(reversedNames)

闭包捕获值

  • 捕获值
  1. 一个闭包能够从上下文捕获一杯定义的常量和变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍能够在其函数体内引用和修改这些值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
  1. 作为一种优化,如果一个值没有改变或者在闭包的外边,Swift可能会使用这个值的拷贝而不是捕获
  2. Swift 也处理了变量的内存管理操作,当遍历不再需要时被释放
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        
        print(runningTotal)
        return runningTotal
    }
    return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
incrementByTen()
incrementByTen()

4.如果你建立了第二个incrementer,它将会有一个新的、独立的runningTotal变量的引用

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

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()

incrementByTen()
  • 闭包是引用类型
  1. 在 Swift 中,函数和闭包都是引用类型
  2. 无论你什么时候赋值一个函数或者闭包给常量或者变量,你实际上都是讲常量和变量设置为对函数和闭包的引用
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        
        print(runningTotal)
        return runningTotal
    }
    return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
incrementByTen()

let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()

incrementByTen()

let also = incrementByTen
also()
  • 闭包是引用类型
  1. 如果你分配了一个闭包给类实例的属性,并且闭包通过引用该实例或者它的成员来捕获实例,你将在闭包和实例间会产生循环引用。

逃逸闭包和自动闭包

  • 逃逸闭包
  1. 当闭包作为一个实际参数传递给一个函数的时候,并且它会在函数返回值后调用,我们就说这个闭包逃逸了。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前些 @escaping 来明确闭包是允许逃逸的
  2. 闭包可以逃逸的一种方法是被存储定义与函数歪的变量里,比如说,很多函数接受闭包实际参数来作为启动异步任务的回调,函数在启动任务后返回,但是闭包要知道任务完成--闭包需要逃逸,一遍稍后调用
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}
  1. 让闭包 @escaping 意味着你必须在比闭包中显示地引用 self
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

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

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

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first
print(instance.x)
  • 自动闭包
  1. 自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包,它不接受任何实际参数,并且当它被调用时,它会返回内部打包的表达式的值
  2. 这个语法的好处在于通过写普通表达式,代替显式闭包而使你省略保卫函数形式参数的括号
  3. 自动闭包允许你延迟处理,因此闭包内部的代码知道你调用它的时候才会运行,对于有副作用或者暂用资源的代码来说很有用,因为它可以允许你控制diamante何时进行求值
var customersInLine = ["one", "two", "three", "four", "five"]
print(customersInLine.count)

let customerProvider = { customersInLine.remove(at: 0)}
print(customersInLine.count)

print("Now serving \(customerProvider())!")
print("Now serving \(customerProvider())!")
print(customersInLine.count)
  1. 当你传一个闭包作为实际参数到函数的时候,你会得到与延迟处理相同的行为
var customersInLine = ["one", "two", "three", "four", "five"]
print(customersInLine.count)

func serve(customer customerProvider: () -> String ) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) })
print(customersInLine.count)
  1. 通过 @ autoclosure 标志标记它的形式参数使用了自动闭包,现在你可以调用函数就像它接受了一个String 实际参数而不是闭包,实际参数自动地转换为闭包,因为 CustomerProvider形式参数的类型被标记为 @autoclosure标记
var customersInLine = ["one", "two", "three", "four", "five"]
print(customersInLine.count)
  
func serve(customer customerProvider: @autoclosure () -> String ) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
print(customersInLine.count)
  • 自动+逃逸
  1. 如果你想要自动闭包允许逃逸,就同时使用 @autoclosure 和 @escaping 标志
var customersInLine = ["one", "two", "three", "four", "five"]
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.")

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

推荐阅读更多精彩内容

  • 距离上次更新博客已经过去了整整十个月,最近打算把更新博客这个习惯捡起来。这篇博客主要是翻译Swift中介绍闭包的官...
    雪山飞狐_91ae阅读 448评论 0 0
  • 闭包是自包含的函数代码块,Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其...
    afluy阅读 1,232评论 0 5
  • 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代...
    CodingIran阅读 318评论 0 0
  • 前言:最近在自学swift,其中swift的闭包其实就是类似于oc中的block,只是两者还是有很多区别的,这里做...
    Code_Caty阅读 5,608评论 0 1
  • 1. 闭包表达式语法 下面是利用闭包对数组进行反向排序的一个例子: 2. 根据上下文推断类型 上文中的参数类型和返...
    keisme阅读 385评论 0 0