美文网首页
从零学习Swift 05:闭包表达式和闭包

从零学习Swift 05:闭包表达式和闭包

作者: 小心韩国人 | 来源:发表于2020-04-21 18:25 被阅读0次
一:闭包表达式的定义

在 Swfit 中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数.闭包表达式的格式为:

{
(参数列表) -> 返回值类型 in
方法体
}

比如说我们使用func定义一个add方法:


func add(a: Int, b: Int) -> Int{
    a + b
}

print(add(a: 10, b: 20))

用闭包表达式也可以定义:


let fn = {
    (a: Int , b: Int) -> Int in
    a + b
}

print(fn(10,20))

二:闭包表达式的简写

定义一个函数如下:

// 接受两个 Int 类型的参数 , 和一个函数类型的参数
func exec(a: Int, b: Int, fn: (Int, Int) -> Int){
    print(fn(a,b))
}

使用闭包表达式完整调用如下:

exec(a: 10, b: 20, fn: {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
})

另外还有4中简写方式:

  1. 省略参数类型 和 返回值类型
    因为闭包表达式已经明确定义了参数类型和返回值类型都是 Int 类型,所以在调用闭包表达式的时候,编译器能自动类型推断,知道具体的类型.所以类型可以省略
exec(a: 10, b: 20) {
    v1, v2 in
    return v1 + v2
}
  1. 省略 return
    因为函数体代码是一个单一表达式,所以我们可以省略 return
exec(a: 10, b: 20) {
    v1, v2 in v1 + v2
}
  1. 省略参数,$0 $1分别代表第一个,第二个参数
exec(a: 10, b: 20) {
    $0 + $1
}
  1. 直接 +
    如果直接写一个运算符,编译器也知道是拿两个参数直接参与运算
exec(a: 10, b: 20, fn: +)
三:尾随闭包

如果一个函数的最后一个实参是一个闭包表达式,并且这个闭包表达式的函数体代码很长,为了增强函数的可读性,这个闭包表达式可以采用尾随闭包.

尾随闭包:尾随闭包是一个书写在函数调用括号外面的闭包表达式.

像上面的exec函数,它的最后一个参数是一个闭包表达式,那我们我们可以使用尾随闭包来增强可读性:(事实上,编译器自动敲出来的就是尾随闭包)

func exec(a: Int, b: Int, fn: (Int, Int) -> Int){
    print(fn(a,b))
}

// 尾随闭包
exec(a: 10, b: 20) { (v1, v2) -> Int in
    return v1 + v2
}

如果函数只有一个参数,而且这个参数是个闭包表达式,并且使用了尾随闭包的写法,那么在调用的时候可以省略小括号():

func add(fn: (Int, Int) -> Int){
    print(fn(20,10))
}

//正常写法
add(fn: {$0 - $1})

//尾随闭包写法
add(){$0 - $1}

//省略小括号()
add{$0 - $1}

示例:
Swift 中的Array有一个排序方法:

func sort(by areInIncreasingOrder: (Self.Element, Self.Element) throws -> Bool)

接收两个参数,返回一个布尔值.假设第一个参数是v1,第二个参数是v2,它的意思是:如果返回true,v1排在v2前面;如果返回false,v1排在v2后面.

常规写法是传入一个函数:

func arraySort(){
    //排序规则函数
    func comp(v1: Int, v2: Int) -> Bool{
        //从大到小排序
        v1 > v2
    }
    
    var array = [4,2,10,19,13,8,22]
    //传入 comp 函数
    array.sort(by: comp(v1:v2:))
    print(array)
}

也可以使用尾随闭包:

//闭包表达式示例  数组排序
func arraySort(){
    var array = [4,2,10,19,13,8,22]
    
    //闭包表达式常规写法
    array.sort(by: {
        (v1: Int, v2: Int) -> Bool in
        return v1 > v2
    })
    
    //因为是最后一个参数,可以采用尾随闭包写法
    array.sort(){
        v1,v2 in v1 > v2
    }
    
    //因为是最后一个参数,并且是唯一一个参数,可以省略小括号
    array.sort{
        v1,v2 in v1 > v2
    }
    
    //使用 $ 替代参数
    array.sort{
        $0 > $1
    }
    
    //直接使用 >
    array.sort(by: >)
    
    print(array)
}

四:闭包

闭包和闭包表达式完全是两个概念:

  1. 闭包表达式: 闭包表达式是定义函数的一种方式;
  2. 闭包: 闭包是一个函数和它所捕获的变量/常量环境组合起来,称作闭包.
    一般指定义在函数内部的函数
    一般它捕获的是外部函数的局部变量/常量

思考一下以下代码的打印结果是什么:

typealias Fn = (Int) -> Int
func getFn() -> Fn{
    
    var num = 0
    
    func sum(_ v1: Int) -> Int{
        num += v1
        return num
    }
    
    return sum
}

var fn = getFn()
print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))

结果如下:


怎么会这样呢?num是在getFn函数内部定义的局部变量,在 149 行调用完getFn()后,num的内存应该被回收了,为什么打印的结果都是在同一块内存上累加的呢?

其实这就是闭包,sum 函数捕获了 num 变量,并且把 num 变量存在了堆空间,所以即使函数调用完毕, num 变量依然没有销毁.

我们来看一下它的汇编代码:

向堆空间申请内存

然后我们再修改一下sum函数的代码,使其不访问外部变量,再看看它的汇编,对比一下:

sum 函数不访问外部变量

汇编如下:

可以看到,当sum函数没有访问外部变量的时候,它的汇编代码很简单.也没有调用allocObject向堆空间申请内存.并且getFn返回的直接就是sum函数的地址.

现在,我们大概也知道了,当 sum 函数访问了外层函数的局部变量 num 时,会调用 allocObject 函数向堆空间申请内存,把 num 变量存储在堆空间,保住 num 变量的命.也就是所谓的 捕获.

下面我们将通过汇编代码验证我们的结论.

如果所示,打两个断点:

断点

运行代码:


getFn 函数返回值

上面没有访问外部变量时,getFn直接返回的就是sum函数的地址.但是这里不一样了,因为访问了外部变量,getFn返回的是一个堆空间的地址.这个地址中有可以找到sum函数地址的线索,并且还要存储捕获的num变量.

继续跳到下一个断点:

执行fn(1)

执行完fn(1),内存中的值变成了 1.

继续跳过断点,会执行fn(2):

执行fn(2

执行fn(3):

执行fn(3)

执行fn(4):

执行fn(4)

通过上面的分析,已经很清晰的知道了当函数访问了外部函数的局部变量后,会向堆空间申请内存,用来捕获外部函数的变量.

思考一下,调用两次getFn()下面代码打印结果是什么?

调用两次 getFn()

我们看看它的汇编代码:

调用两次getFn()

返回了两个Fn对象:

执行 fn1(1):

执行 fn1(1)

执行 fn2(2):

执行 fn2(2)

执行 fn1(3):

执行 fn1(3)

执行 fn2(4):

执行 fn2(4

可以看到,执行多少次getFn(),就会创建多少个Fn对象,并且每个对象之间互不影响.他们都有自己的内存空间,都有自己的捕获对象.

上面的fn1 , fn2和它捕获的变量组合在一起就是一个闭包,其实闭包和类的实例对象很相似,我们通过两张图对比一下:

闭包 和 实例对象对比

另外他们都存储在堆空间,并且内存布局也一样,所以我们可以把闭包想象成一个实例对象,这样更容易理解.

相关文章

  • Swift学习笔记(1)

    SWift学习笔记 闭包 闭包表达式 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 ...

  • 闭包

    闭包 本节内容包括: 闭包表达式 尾随闭包 值捕获 闭包是引用类型 Swift 中的闭包与 C 和 Objecti...

  • swift4 闭包

    swift 闭包 闭包:swift 中 函数是闭包的一种类似于oc的闭包闭包表达式(匿名函数) -- 能够捕获上下...

  • 从零学习Swift 05:闭包表达式和闭包

    一:闭包表达式的定义 在 Swfit 中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数.闭包表达...

  • swift 闭包与闭包表达式

    闭包与闭包表达式 在swift里闭包大家都很熟悉,相当于oc中的block。闭包表达式又是啥?很多人把闭包表达式等...

  • 使用Playground快速练习Swift语法--闭包和枚举

    闭包 定义:闭包是自包含的函数代码块,可以在代码中被传递和使用。 闭包表达式语法 Swift闭包使用{}包含,in...

  • Swift中的闭包

    在Swift中有两种闭包,逃逸闭包(@escaping)和非逃逸闭包(@nonescaping)。从Swift 3...

  • Swift语法 -- [07 - 闭包]

    在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数 1 闭包表达式 闭包表达式 闭包表...

  • Swift 5基础语法要点整理—闭包

    闭包 闭包表达式 在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数 闭包表达式的简写...

  • Swift5.x-枚举(中文文档)

    引言 继续学习Swift文档,从上一章节:闭包,我们学习了Swift闭包相关的内容,如闭包的定义和使用、闭包的简写...

网友评论

      本文标题:从零学习Swift 05:闭包表达式和闭包

      本文链接:https://www.haomeiwen.com/subject/ymfdihtx.html