swift-闭包及其本质

Posted by sunzhongliang on January 5, 2020

闭包

闭包表达式(Closure Expression)

swift中可以通过func定义一个函数, 也可以通过一个闭包表达式定义一个函数

  • 通过func的形式来实现一个函数传入两个Int参数返回两个参数之和
    // 定义一个函数传入两个Int类型参数,返回这两个参数的和
    func sum(_ v1: Int, _ v2: Int) -> Int { v1 + v2 }
    // 调用
    sum(1, 2) // 3
    
  • 通过闭包表达式的形式来实现一个函数传入两个Int参数返回两个参数之和
    // 定义一个闭包表达式传入两个Int类型参数,返回这两个参数的和
    var fn = {
      (v1: Int, v2: Int) -> Int in
      return v1 + v2
    }
    // 调用
    fn(10, 20)
    

swift闭包表达式看起来跟OC当中的block有点像,通过 in 将函数体代码和(参数列表与返回值类型)区分开来;
swift当中的闭包表达式的格式是这样的:

{
    (参数列表) -> 返回值类型 in
    函数体代码
}

// 如:
{
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}(10, 20)

闭包表达式的简写

// 定义一个exec函数,传入两个Int类型的参数和一个函数
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int){
    print(fn(v1, v2))
}
// 调用
exec(v1: 10, v2: 20, fn: {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
})
// 还可以这样简写
exec(v1: 10, v2: 20, fn: {
    v1, v2 in return v1 + v2
})
// 这样也是可以的
exec(v1: 10, v2: 20, fn: {$0 + $1})
// 如果说前面的写法都不过分的话,那么这种写法同事看到肯定会打你的
exec(v1: 10, v2: 20, fn: +)

尾随闭包

如果将一个很长的闭包表达式作为函数的最后一个实参, 使用尾随闭包可以增强函数的可读性
尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式

func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int){
    print(fn(v1, v2))
}
exec(v1: 10, v2: 20, fn: {$0 + $1})

如果闭包表达式是函数的唯一实参, 而且使用了尾随闭包的语法,那就不需要在函数名后面写圆括号

func exec(fn: (Int, Int) -> Int){
    print(fn(1, 2))
}
// 调用
exec(fn: { $0 + $1 })
// 或者
exec() { $0 + $1 }
// 或者
exec { $0 + $1 }

示例:数组的排序
swift当中系统为我们提供了sort函数来实现排序,调用方法如下:

var arr = [10,1,5,22,55,0]
// 使用系统提供的sort方法
arr.sort(by: {
    (i1: Int, i2: Int) -> Bool in
    return i1 < i2
})
arr.sort(by: {i1, i2 in return i1 < i2})
arr.sort(by: {i1, i2 in i1 < i2})
arr.sort(by: { $0 < $1 })
arr.sort(by: <)
arr.sort() { $0 < $1 }
arr.sort { $0 < $1 }

利用闭包表达式,我们可以实现自定义排序规则,比如排序一个复杂的Person等类型

/// 返回true: i1排在i2前面
/// 返回false:i1排在i2后面
func compare(i1: Int, i2: Int) -> Bool {
    // 代表大的排在前面
    return i1 > i2
}
arr.sort(by: compare)

闭包(Closure)

前面讲的是闭包表达式,跟闭包是两个不同的概念
闭包:一个函数和它所捕获的变量\常量环境组合起来,称为闭包;一般指定义在函数内部的函数,捕获的是外层函数的局部变量\常量

// 定义一个Fn函数,接收Int参数,返回Int类型
typealias Fn = (Int) -> Int
func getFn() -> Fn {
    var num = 0 // 局部变量
    func plus(_ i: Int) -> Int {
        num += i
        return num
    }
    return plus
} // 返回的plus和num形成了闭包
// 调用
var fn = getFn()
print(fn(1)) // 输出1
print(fn(2)) // 输出3
print(fn(3)) // 输出6
print(fn(4)) // 输出10

正如上段代码所示,numgetFn函数所捕获,形成了闭包;在不停的调用getFn函数内部的fn函数时,num会不停的进行累加。可以把闭包想象为一个类的实例对象
其本质是闭包在捕获变量之后,会通过allocObject函数,在堆中开辟出一段空间,然后将num中的值拷贝一份到堆空间中,之后对于num的操作,都是对堆空间的值进行操作。

闭包的作用就是为了防止变量污染,内部函数拥有了外部函数的作用域

本文首次发布于 孙忠良 Blog, 作者 [@sunzhongliang] , 转载请保留原文链接.