2021年5月26日星期三

Swift系列八

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

一、闭包表达式(Closure Expression)

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

1.1. 闭包表达式的格式

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

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

定义一个普通的函数:

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

定义闭包:

var sum = { (v1: Int, v2: Int) -> Int in return v1 + v2}let result = sum(10, 20)print(result)// 输出:30

1.3. 闭包表达式的简写

定义函数:

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

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

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

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

exec(v1: 10, v2: 20, fn: { (v1: Int, v2: Int) -> Int in return v1 + v2})// 输出:30

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

1.3.1. 简写一

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

1.3.2. 简写二

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

exec(v1: 10, v2: 20, fn: { v1, v2 in v1 + v2})// 输出:30

1.3.3. 简写三

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

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

1.3.4. 简写四(不建议)

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

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

二、尾随闭包

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

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

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

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

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

2.2. 特点二(唯一实参)

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

定义函数:

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

调用方式:

// 方式一:exec(fn: { $0 + $1 })// 方式二:exec() { $0 + $1 }// 方式三:exec { $0 + $1 }/* 输出: 30 30 30 */

三、闭包(Closure)

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

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

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

示例代码:

typealias Fn = (Int) -> Intfunc getFn() -> Fn { var num = 0 func plus(_ i: Int) -> Int {  num += 1  return num } return plus}var fn = getFn()print(fn(1))print(fn(2))print(fn(3))print(fn(4))/* 输出: 1 3 6 10 */

为什么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. 闭包和类的比较

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

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

四、自动闭包

示例代码:

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

把上面的代码修改如下:

func getNumber() -> Int { print("getNumber") let a = 10 let b = 10 return a + b}let result1 = getFirst(10, getNumber())print(result1)/* 输出: getNumber 10 */let result2 = getFirst(-1, getNumber())print(result2)/* 输出: getNumber 20 */

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

优化代码:

typealias VoidFunc = () -> Intfunc getFirst(_ v1: Int, _ v2: VoidFunc) -> Int { print("getFirst") return v1 > 0 ? v1 : v2()}func getNumber() -> Int { print("getNumber") let a = 10 let b = 10 return a + b}getFirst(10, getNumber)/* 输出: getFirst */getFirst(-1, getNumber)/* 输出: getFirst getNumber */

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

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

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

自动闭包代码:

typealias VoidFunc = () -> Intfunc getFirst(_ v1: Int, _ v2: @autoclosure VoidFunc) -> Int { print("getFirst") return v1 > 0 ? v1 : v2()}getFirst(10, 20) // 10getFirst(-1, 10) // 10

自动闭包特点

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

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

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

五、应用

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

定义函数:

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

3.1. 系统排序

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

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

3.2. 自定义排序

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

Array提供的函数:

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

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

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

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

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

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

arr.sort(by: { (i1: Int, i2: Int) -&gt; Bool in return i1 &gt; i2})arr.sort(by: { i1, i2 in return i1 &gt; i2 })arr.sort(by: { i1, i2 in i1 &gt; i2 })arr.sort(by: { $0 &gt; $1 })arr.sort(by: &gt;)arr.sort() { $0 &gt; $1 }arr.sort { $0 &gt; $1 }// 输出:[80, 72, 52, 20, 19, 3]








原文转载:http://www.shaoqun.com/a/760857.html

跨境电商:https://www.ikjzd.com/

google趋势:https://www.ikjzd.com/w/397

airwallex:https://www.ikjzd.com/w/1011


什么是闭包?闭包表达式又是什么?一、闭包表达式(ClosureExpression)在Swift中,可以通过func定义一个函数,也可以通过闭包表达式定义一个函数。1.1.闭包表达式的格式{(参数列表)->返回值类型in函数体代码}1.2.闭包表达式和函数的比较定义一个普通的函数:funcsum(_v1:Int,_v2:Int)->Int{v2+v2}letresult=sum(10,
盘古集团:https://www.ikjzd.com/w/1448
myyearbook:https://www.ikjzd.com/w/726
一淘比价网:https://www.ikjzd.com/w/1698
和陌生人一起干老婆,老婆好舒服:http://lady.shaoqun.com/m/a/274782.html
shopee进阶运营技巧(流量获取):https://www.ikjzd.com/tl/107775
他疯狂的追求她 口述第一次与网友约会好难忘:http://lady.shaoqun.com/a/275173.html

没有评论:

发表评论