经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
Swift系列八 - 闭包
来源:cnblogs  作者:1024星球  时间:2021/5/31 9:06:26  对本文有异议

什么是闭包?闭包表达式又是什么?

一、闭包表达式(Closure Expression)

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

1.1. 闭包表达式的格式

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

1.2. 闭包表达式和函数的比较

定义一个普通的函数:

  1. func sum(_ v1: Int, _ v2: Int) -> Int { v2 + v2 }
  2. let result = sum(10, 20)
  3. print(result)
  4. // 输出:30

定义闭包:

  1. var sum = {
  2. (v1: Int, v2: Int) -> Int in
  3. return v1 + v2
  4. }
  5. let result = sum(10, 20)
  6. print(result)
  7. // 输出:30

1.3. 闭包表达式的简写

定义函数:

  1. func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
  2. print(fn(v1, v2))
  3. }

要想使用exec函数,则必须传入两个Int类型的参数和一个返回Int类型的函数,然后exec内部执行了传入的函数。

  1. func sum(_ a: Int, _ b: Int) -> Int {
  2. return a + b
  3. }
  4. exec(v1: 10, v2: 20, fn: sum)
  5. // 输出:30

按照以往的知识,我们需要定义一个函数,然后把函数传给exec就行了。其实我们也可以使用闭包表达式。

  1. exec(v1: 10, v2: 20, fn: {
  2. (v1: Int, v2: Int) -> Int in
  3. return v1 + v2
  4. })
  5. // 输出:30

上面的闭包表达式还可以简写:

1.3.1. 简写一

  • 省略参数类型和返回值;
  • 编译器会自动推断闭包表达式中参数类型和返回值类型。
  1. exec(v1: 10, v2: 20, fn: {
  2. v1, v2 in return v1 + v2
  3. })
  4. // 输出:30

1.3.2. 简写二

如果函数的返回值是一个单一表达式,可以省略return

  1. exec(v1: 10, v2: 20, fn: {
  2. v1, v2 in v1 + v2
  3. })
  4. // 输出:30

1.3.3. 简写三

如果闭包表达式不想写参数,可以使用美元符$序号代替,序号从0开始,代表参数位置。

  1. exec(v1: 10, v2: 20, fn: { $0 + $1 })
  2. // 输出:30

1.3.4. 简写四(不建议)

简单的闭包表达式还可以直接使用运算符代替。

  1. exec(v1: 10, v2: 20, fn: +)
  2. // 输出:30

二、尾随闭包

2.1. 特点一(最后一个实参)

如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性。

尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式。

以调用上面的exec函数为例:

  1. exec(v1: 10, v2: 20) {
  2. $0 + $1
  3. }
  4. // 输出:30

2.2. 特点二(唯一实参)

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

定义函数:

  1. func exec(fn: (Int, Int) -> Int) {
  2. print(fn(10, 20))
  3. }

调用方式:

  1. // 方式一:
  2. exec(fn: { $0 + $1 })
  3. // 方式二:
  4. exec() { $0 + $1 }
  5. // 方式三:
  6. exec { $0 + $1 }
  7. /*
  8. 输出:
  9. 30
  10. 30
  11. 30
  12. */

三、闭包(Closure)

闭包和闭包表达式严格意义上来讲并不是同一个概念。

一个函数和它所捕获的变量/常量环境组合起来,称为闭包。

  • 一般指定义在函数内部的函数;
  • 一般它捕获的是外层函数的局部变量/常量。

示例代码:

  1. typealias Fn = (Int) -> Int
  2. func getFn() -> Fn {
  3. var num = 0
  4. func plus(_ i: Int) -> Int {
  5. num += 1
  6. return num
  7. }
  8. return plus
  9. }
  10. var fn = getFn()
  11. print(fn(1))
  12. print(fn(2))
  13. print(fn(3))
  14. print(fn(4))
  15. /*
  16. 输出:
  17. 1
  18. 3
  19. 6
  20. 10
  21. */

为什么var num = 0作为局部变量还能一直累加?不是应该在函数执行完成后就被释放了么?我们通过汇编一探究竟。

3.1. 汇编分析闭包

3.1.1. 如果内部函数没有捕获外部变量:



通过分析可以看到,函数返回的是一个地址,也就是变量fn里面存放的是函数地址。

3.1.2. 如果内部函数捕获外部变量:



汇编代码就变得复杂一点了,并且出现了swift_allocObject关键字,也就意味着在堆空间申请了一块内存,内存存放的是num的值。每次调用fn,访问的num都是同一块内存地址,所以才会出现局部变量也能一直累加的效果。

3.1.3. 证明swift_allocObject存放的是num

第一步:源代码断点:

第二步:查看swift_allocObject返回的地址:

第三步:查看rax地址存放的初始化值:

第四步:执行fn(1)后:

第五步:执行fn(2)后:

结论: 内部函数一旦捕获了外部的局部变量,要想持续使用这个变量,就需要延迟变量的生命周期,所以在堆空间分配一块内存来存放局部变量的值。

思考:为什么可以访问同一块内存空间?

var fn = getFn()fn占用16个字节,前8个字节存放返回的函数地址(plus的封装),后8个字节存放堆空间(num)的地址。如果var fn2 = getFn()fn1fn2前8个字节可能相同,不同的是后面的8个字节。

3.2. 闭包和类的比较

可以把闭包想象成是一个类的实例对象。

  • 内存在堆空间;
  • 捕获的局部变量/常量就是对象的成员(存储属性);
  • 组成闭包的函数就是类内部定义的方法。
  1. class Closure {
  2. var num = 0
  3. func plus(_ i: Int) -> Int {
  4. num += i
  5. return num
  6. }
  7. }
  8. var cs = Closure()
  9. print(cs.plus(1))
  10. print(cs.plus(2))
  11. print(cs.plus(3))
  12. print(cs.plus(4))
  13. /*
  14. 输出:
  15. 1
  16. 3
  17. 6
  18. 10
  19. */

四、自动闭包

示例代码:

  1. // 如果第一个数大于0,返回第一个数,否则返回第二个数
  2. func getFirst(_ v1: Int, _ v2: Int) -> Int {
  3. return v1 > 0 ? v1 : v2
  4. }
  5. getFirst(10, 20) // 10
  6. getFirst(-2, 20) // 20
  7. getFirst(0, -4) // -4

把上面的代码修改如下:

  1. func getNumber() -> Int {
  2. print("getNumber")
  3. let a = 10
  4. let b = 10
  5. return a + b
  6. }
  7. let result1 = getFirst(10, getNumber())
  8. print(result1)
  9. /*
  10. 输出:
  11. getNumber
  12. 10
  13. */
  14. let result2 = getFirst(-1, getNumber())
  15. print(result2)
  16. /*
  17. 输出:
  18. getNumber
  19. 20
  20. */

分析:不管第一个数是否大于0,都会执行第二个参数传入的函数,这样整体有点浪费(性能/空间)。我们可以尝试把函数第二个入参类型修改为函数类型。

优化代码:

  1. typealias VoidFunc = () -> Int
  2. func getFirst(_ v1: Int, _ v2: VoidFunc) -> Int {
  3. print("getFirst")
  4. return v1 > 0 ? v1 : v2()
  5. }
  6. func getNumber() -> Int {
  7. print("getNumber")
  8. let a = 10
  9. let b = 10
  10. return a + b
  11. }
  12. getFirst(10, getNumber)
  13. /*
  14. 输出:
  15. getFirst
  16. */
  17. getFirst(-1, getNumber)
  18. /*
  19. 输出:
  20. getFirst
  21. getNumber
  22. */

结果:只有需要的时候才会执行对应的代码。

但是,如果这样修改后,每次都需要传入一个函数会有点麻烦。Swift提供了自动闭包功能,可以把普通变量自动包裹成闭包,这样就能满足上面代码的所有的功能了。

关键字: @autoclosure
用法:在函数前面加上@autoclosure关键字即可。

自动闭包代码:

  1. typealias VoidFunc = () -> Int
  2. func getFirst(_ v1: Int, _ v2: @autoclosure VoidFunc) -> Int {
  3. print("getFirst")
  4. return v1 > 0 ? v1 : v2()
  5. }
  6. getFirst(10, 20) // 10
  7. getFirst(-1, 10) // 10

自动闭包特点

  • @autoclosure会将普通参数(例如,20)封装成闭包{ 参数 }(例如,{ 20 });
  • @autoclosure只支持() -> T(无参有返回值)格式的参数;
  • @autoclosure并非只支持最后一个参数,和位置没有任何关系;
  • @autoclosure、无@autoclosure,构成函数重载;
  • 为了避免与期望冲突,使用了有@autoclosure的地方最好明确注释清楚:这个值会被延迟执行(有可能不执行)。

延伸: 空合并运算符??使用了@autoclosure技术。

  1. public func ?? <t>(optional: T?, defaultValue: @autoclosure () throws -&gt; T?) rethrows -&gt; T?

五、应用

通过数组的排序看下闭包表达式是如何使用的。

定义函数:

  1. var arr = [20, 52, 19, 3, 80, 72]

3.1. 系统排序

在Swift中,Array为开发者提供了sort()排序函数,开发者可以直接使用。

  1. arr.sort()
  2. print(arr)
  3. // 输出:[3, 19, 20, 52, 72, 80]

3.2. 自定义排序

sort()是升序的,如果要降序呢?我们可以使用另外一个函数进行自定义排序。

Array提供的函数:

  1. func sort(by areInIncreasingOrder: (Element, Element) throws -&gt; Bool) rethrows

可以看到,该函数让传入一个闭包表达式。使用规则如下:

  • 返回true:第一个元素排在第二个元素前面;
  • 返回false:第一个元素排在第二个元素后面。

调用方式一(普通函数):

  1. func compare(i1: Int, i2: Int) -&gt; Bool {
  2. return i1 &gt; i2
  3. }
  4. arr.sort(by: compare)
  5. print(arr)
  6. // 输出:[80, 72, 52, 20, 19, 3]

调用方式二(闭包表达式):

  1. arr.sort(by: {
  2. (i1: Int, i2: Int) -&gt; Bool in
  3. return i1 &gt; i2
  4. })
  5. arr.sort(by: { i1, i2 in return i1 &gt; i2 })
  6. arr.sort(by: { i1, i2 in i1 &gt; i2 })
  7. arr.sort(by: { $0 &gt; $1 })
  8. arr.sort(by: &gt;)
  9. arr.sort() { $0 &gt; $1 }
  10. arr.sort { $0 &gt; $1 }
  11. // 输出:[80, 72, 52, 20, 19, 3]

原文链接:http://www.cnblogs.com/idbeny/p/swift-syntax-closure.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号