Swift 学习笔记 (闭包)

闭包是可以在你的代码中被传递和饮用的功能性独立模块。Swift中的闭包和C以及Objective-C中的Block很像,和其他语言中的匿名函数也很像。

闭包能捕获和存储定义在其上下文中的任何常量和变量的饮用,这也就是所谓的闭合并包裹那些常量和变量,因此称为闭包,Swift能够为你处理所有关于捕获内存管理的操作。

在上一篇函数的介绍中 全局和内嵌函数 实际上就是特殊的闭包,闭包符合如下三种形式中的一种

全局函数是一个有名字但不会捕获任何值的闭包

内嵌函数是一个有名字且能从7其上层函数捕获值的闭包

闭包表达式是一个轻量级语法所写的可以捕获其上下文中常量货变量值的没有名字的闭包

Swift 的闭包表达式拥有简洁的风格,鼓励在常见场景中实现简洁,无累赘的语法。常见的优化包括:

利用上下文推断形式参数和返回值的类型;
单表达式的闭包可以隐式返回;
简写实际参数名;
尾随闭包语法。

Sorted方法

Swift 的标准库提供了一个叫做 sorted(by:) 的方法,会根据你提供的排序闭包将已知类型的数组的值进行排序。一旦它排序完成, sorted(by:) 方法会返回与原数组类型大小完全相同的一个新数组,该数组的元素是已排序好的。原始数组不会被 sorted(by:) 方法修改。

下面这个闭包表达式的栗子使用 sorted(by:) 方法按字母排序顺序来排序一个 String 类型的数组。这是将被排序的初始数组:

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

闭包的表达式语法

//闭包的表达式语法
{(parameters) ->(return type) in
    statements
}

闭包表达式语法能够使用常量形式参数、变量形式参数和输入输出形式参数,但不能提供默认值。可变形式参数也能使用,但需要在形式参数列表的最后面使用。元组也可被用来作为形式参数和返回类型。

下面的例子和上面的例子效果是一样的。

let names = ["Chris","Alex","Ewa","Barry","Daniella"]

names.sorted(by: {(s1:String,s2:String) -> Bool in
    return s1 > s2
})

需要注意的是行内闭包的形式参数类型和返回类型的声明与 backwards(_:_:) 函数的申明相同。在这两个方式中,都书写成 (s1: String, s2: String) -> Bool。总之对于行内闭包表达式来说,形式参数类型和返回类型都应写在花括号内而不是花括号外面。

闭包的函数整体部分由关键字 in 导入,这个关键字表示闭包的形式参数类型和返回类型定义已经完成,并且闭包的函数体即将开始。

从语境中推断类型

因排序闭包为实际参数来传递给函数,故 Swift 能推断它的形式参数类型和返回类型。 sorted(by:) 方法期望它的第二个形式参数是一个 (String, String) -> Bool 类型的函数。这意味着 (String, String)和 Bool 类型不需要被写成闭包表达式定义中的一部分,因为所有的类型都能被推断,返回箭头 ( ->) 和围绕在形式参数名周围的括号也能被省略:

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

从单表达式闭包隐式返回

单表达式闭包能够通过从它们的声明中删掉 return 关键字来隐式返回它们单个表达式的结果,前面的栗子可以写作:

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

尾随闭包

如果你需要将一个很长的闭包表达式作为函数最后一个实际参数传递给函数,使用尾随闭包将增强函数的可读性。尾随闭包是一个被书写在函数形式参数的括号外面(后面)的闭包表达式:

func someFunctionThatTakesAClosure(closure:()->()){
    
}

调用

someFunctionThatTakesAClosure {
    print("heihei")
}

具体例子

func loadData(completion:@escaping(_ result:[String])->()){
    //耗时操作
    DispatchQueue.global().async {
        print("耗时操作")
    }
    
    let json = ["闭包","传值","Demo"]
    
    //回到主线程更新UI
    DispatchQueue.main.async {
        print("json数据 (json)")
        completion(json)
    }
}

loadData { (result) in
    print("直接操作 (result)")
}

捕获值

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

在Swift中,一个能捕获值的闭包最简单的模型是内嵌函数。例子

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

makeIncrementer(forincrementer: 6)()

incrementer() 函数是没有任何形式参数, runningTotal 和 amount 不是来自于函数体的内部,而是通过捕获主函数的 runningTotal 和 amount 把它们内嵌在自身函数内部供使用。当调用 makeIncrementer  结束时通过引用捕获来确保不会消失,并确保了在下次再次调用 incrementer 时, runningTotal 将继续增加

注意:作为一种优化 如果一个值没有改变或者在闭包的外面 Swift可能会使用这个值的拷贝而不是捕获。

Swift也处理了变量的内存管理操作,当变量不再需要时会被释放。

逃逸闭包

当闭包作为一个实际参数传递给一个函数的时候,我们就说这个闭包逃逸了。因为它可以在函数返回之后被调用。当你声明一个接受闭包作为形式参数的函数时,你可以在这个形式参数前写 @escaping来明确闭包时允许逃逸的。

闭包可以逃逸的一种方式是被存储在定义函数外的变量里。例如

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

函数 someFunctionWithEscapingClosure(_:) 接收一个闭包作为实际参数并且添加它到声明在函数外部的数组里。如果你不标记函数的形式参数为 @escaping ,你就会遇到编译时错误。

让闭包 @escaping 意味着你必须在闭包中显示的引用self 比如在下面的代码中,传给someFunctionWithEscapingClosure(_:) 的闭包是一个逃逸闭包,也就是说它需要显式地引用 self 。相反,传给 someFunctionWithNonescapingClosure(_:) 的闭包是非逃逸闭包,也就是说它可以引式地引用 self 。

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

func someFunctionWithNoescapingClosure(closure:() ->Void) {
    closure()
}
class someClass {
    var x = 10
    func doSomething() {
        someFunctionWithNoescapingClosure {
            x = 100
        }
        someFunctionWithEscapingClosure {
            self.x = 200
        }
    }
}

自动闭包

自动闭包是一种自动创建的用来把作为实际参数传递给函数的表达式打包的闭包 它不接受任何实际参数,并且当他被调用的时候,他会返回内部打包的表达式的值。这个语法的好处在与通过写普通表达式替代显示闭包而使你省略包围函数形式参数的括号。

调用一个带有自动闭包的函数是很常见的,但实现这类函数就不那么常见了。比如说, assert(condition:message:file:line:) 函数为它的 condition  和 message 形式参数接收一个自动闭包;它的 condition 形式参数只有在调试构建是才评判,而且 message 形式参数只有在 condition 是 false 时才评判。

自动闭包允许你延迟处理,因此闭包内部的代码直到你调用它的时候才会运行。对于有副作用或者占用资源的代码来说很有用,因为它可以允许你控制代码何时才进行求值。下面的代码展示了闭包如何延迟求值。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine)

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

print("now serving (customerProvider())!")
customersInLine.count // 4

尽管 customersInLine 数组的第一个元素以闭包的一部分被移除了,但任务并没有执行直到闭包被实际调用。如果闭包永远不被调用,那么闭包里边的表达式就永远不会求值。

当你传一个闭包作为实际参数到函数的时候,你会得到与延迟处理相同的行为。

如果你想要自动闭包允许逃逸,就同时使用 @autoclosure 和 @escaping 标志。

这些只是闭包的一些简单概念和简单例子,在实际应用中还要注意它们的内存管理等等。会在后面的与oc中的block对比中进行学习。敬请关注。

原文地址:https://www.cnblogs.com/huanying2000/p/6387486.html