从零开始的SWIFT开发之闭包

1,304 阅读4分钟

闭包表达式

截屏2022-06-07 15.27.35.png

1.闭包在调用的时候,不需要写形参v1还是v2。

闭包作为函数参数

  func exec(v1:Int,v2:Int,fn:(Int,Int) -> Int){

            print(fn(v1,v2))
  }

那么在调用exec函数的时候已下两种方式都可以,可以对两个参数做一些操作,最后输出结果。因为他们在闭包中的参数类型和返回值类型都确定了,并且都是Int类型,所以可以直接用0表示第一个参数,0表示第一个参数,1表示第二个参数。


        exec(v1: 10, v2: 10) { v1, v2 **in**

            v1 + v2

        }

        

        exec(v1: 10, v2: 20, fn: {$0+$1})

尾随闭包

闭包表达式很长,作为最后一个实参,使用尾随闭包可以增加可读性。具体是写在括号后面。

截屏2022-06-07 16.59.35.png

闭包

一般是发生在函数嵌套中,返回的是内部函数,并且内部函数捕获了外部函数的变量或者常量。

举个栗子


        func outside() -> () -> () {

            var a = 10

            func inside() {

                print(a)

                a += 1

            }

            return inside

        }
        

举个🌰

  typealias Fn = (Int) -> Int

        func getFn() -> Fn{

            var num = 0

            func plus(_ i:Int)->Int{

                num += i

                return num

            }

            return plus

        }

        

        let vn = getFn()

        print(vn(1))

        print(vn(2))

        print(vn(3))

        print(vn(4))

打印结果: 1 ,3 ,6, 10.

声明了一个函数的别名Fn,并且作为函数getFn的返回值类型,通过getFn可以拿到plus函数,然后通过给plus函数传值拿到返回值,然后通过print打印返回值。

问题就是,这个num作为局部变量,为什么可以在相加的时候,num保持了原有的数据而不是销毁回收了?

为了弄清楚这个问题,还是可以通过汇编的方式去分析,现在的问题就是访问num的时候,似乎找到了同一片内存空间,所以可以相加。先把num注释,看一下汇编的情况。

截屏2022-06-08 17.52.49.png

截屏2022-06-08 17.55.07.png

打印出的结果,leaq是返回了rip+0x15这个地址,也就是说返回的是这个plus方法的地址。

那么再加上num看一下

截屏2022-06-08 17.56.10.png

对比之前,可以看到有一步操作是swift_allocObject,也就是说去堆空间申请了一片地址,也就是说只要用到了num,就会开辟这块堆空间,getFn函数调用一次,就会alloc一次,因为用的是同一个vn,所以他们能够保证访问的都是那块堆空间的地址,num所以会累加。并且注意到这边有很多关于movq的操作,movq就是找到这个地址并且把地址里的数据拿出来。这里的操作就是把num的数据存到堆空间上。

分析闭包结构

既然vn = getFn(),也大概知道vn就是getFn里的plus函数,那么vn到底是什么呢?还是说只是一个函数而已,那么首先可以打印一下他的大小看一下。

截屏2022-06-09 15.12.29.png

如果没有捕获外部变量,这个vn的大小也是16个字节,前八个字节存储的是这个返回的方法的地址,后八字节是0.

如果是闭包,前八字节是与这个方法相关的地址,可以根据这个地址找到这个方法,后八个字节是堆空间的地址。在调用的时候,除了这个方法所需要的参数以外,还会传递后八个字节也就是堆空间的地址给这个方法。

闭包表达式 & 闭包

定义函数有两种方式,第一是func关键字定义,第二种就是闭包表达式定义,他们都是函数,有参数和返回值函数体,但是闭包除了函数以外,还有他所捕获的局部变量。局部变量被捕获以后,是将原来栈空间的这个变量里的内容拷贝到堆空间,以后访问直接到堆空间访问,保证这个闭包生成的变量下次访问还是这块堆空间的地址,随着函数结束,原来这块栈空间的局部变量一样还是会被回收

自动闭包 @autoclosure


        func getNum(_ v1:Int,_ v2:()->Int)->Int{

           return v1 > v2() ? v1 : v2()

        }
        print(getNum(10, {20}))
        

如果传参里加入了闭包表达式,这样做的好处是可以延迟执行闭包函数,如果getnum的两个参数,后者是一个普通函数调用,那么他一定会执行,但是有的时候,我们希望通过内部判断,不让他执行,那么可以通过闭包直接在getNum里面做判断即可。

还有一种写法,也就是自动闭包的方式,@autoclosure 他会自动帮你加上{},并且返回一个类型,需要注意的是,自动闭包只接收()->T这种方式,但不需要一定是末尾参数才可以加,另外自动闭包是可以重载的。


        func getNum(_ v1:Int,_ v2: @autoclosure ()->Int)->Int{

            return v1 > v2() ? v1 : v2()

        }

        print(getNum(10, 20))