什么是闭包?闭包表达式又是什么?
一、闭包表达式(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()
,fn1
和fn2
前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 -> T?) rethrows -> 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 -> Bool) rethrows
可以看到,该函数让传入一个闭包表达式。使用规则如下:
- 返回true:第一个元素排在第二个元素前面;
- 返回false:第一个元素排在第二个元素后面。
调用方式一(普通函数):
func compare(i1: Int, i2: Int) -> Bool { return i1 > i2}arr.sort(by: compare)print(arr)// 输出:[80, 72, 52, 20, 19, 3]
调用方式二(闭包表达式):
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 }// 输出:[80, 72, 52, 20, 19, 3]
原文转载:http://www.shaoqun.com/a/760857.html
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
没有评论:
发表评论