swift闭包定义和常见用法

1、闭包是自包含的功能代码块,跟C和Objective-C中的代码块(blocks)和其他一些语言中的匿名函数相似。
2、闭包可以作为函数的参数也可以作为函数的返回值。
3、可以像oc中用于回调和反向传值

一、闭包表达式

闭包表达式可以理解为闭包的表现形式
语法形式为

{
   (参数列表) -> 返回值类型 in 函数体代码
}
  • 参数可以有多个,多个参数用,隔开
  • 参数列表的小括号可以省略
  • 返回值类型也可以省略
  • 当没有参数时in可以省略
  • in可以看作是一个分隔符,将函数体和前面的参数、返回值分割开来

1、有参有返回值

// 有参有返回值
let testOne: (String,String) -> String = {(str1,str2) -> String in
    return str1 + str2
}
print(testOne("test","One"))

:的右边是闭包的类型,=右边就是一个闭包的表达式,也可以理解为一个闭包。
=右边是严格按照了闭包表达式来写的,有参数,有括号,有返回值。下面再看下闭包表达式的简写

let testOne = {str1,str2 in
    return str1 + str2
}
print(testOne("test","One"))

这个跟上一个是等价的,闭包表达式省去了参数的括号和返回值。:右边的闭包类型省去了是因为swift编译器能自动根据=右边去判断类型

2、无参无返回值

//无参无返回值
let testThree: () -> Void = {
    print("testThree")
}
testThree()

:右边类型省去后

let testThree = {
    print("testThree")
}
testThree()

因为没有参数in可以省略

二、闭包作为函数参数

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

这个函数有三个参数,第一个参数是一个函数
下面是exec函数的调用

exec(fn: { a, b in
    return a + b
}, v1: 1, v2: 2)

exec函数调用时,{}里就是一个闭包表达式,可以看作是第一个参数函数的实现。
这样的函数调用形式看起来很不友好,如果闭包表达式有很多行的话,会更加不友好,不利于代码的阅读。swift提供了一个尾随闭包的概念

1、尾随闭包

  • 当函数的最后一个参数是函数时,在函数调用时可以把闭包表达式写在()外面
  • 尾随闭包是一个书写在函数括号之后的闭包表达式
  • 如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

这次把exec函数的第一个参数fn放到了最后,下面可以看下调用方式和上次的有什么不同

exec(v1: 1, v2: 2) { a, b in
    return a + b
}

跟上一次的函数调用对比,这次是把闭包表达式写在了函数括号之后,增强了代码的可读性,这就是尾随闭包。

  • 可以使用简化参数名,如$0, $1(从0开始,表示第i个参数...)
exec(v1: 1, v2: 2) {
    return $0 + $1
}

还有很多的简写就不一一列举了,太简写了也不利于代码阅读

  • 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,可以将函数名后边的圆括号省略
func exec(fn: (Int, Int) -> Int) {
    print(fn(1,2))
}

exec { a, b in
    return a + b
}

2、逃逸闭包

  • 如果一个闭包被作为一个函数的参数,并且在函数执行完之后才被执行,那么这种情况下的闭包就被称为逃逸闭包
  • 在参数名的:后面用@escaping来修饰说明逃逸闭包
    一般在涉及到异步操作时,闭包放在异步线程里,在这种情况下就会出现逃逸闭包,特别是在网络请求时会出现这种情况
func exec(fn: @escaping () -> ()) {
    //延迟5s
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) {
        //5s后调用闭包
        fn()
    }
    print("函数执行完毕")
}

exec {
    print("闭包执行完毕")
}

这段代码会先打印"函数执行完毕",5秒后再执行闭包打印"闭包执行完毕"

3、自动闭包

  • 在参数名的:后面用@autoclosure来修饰说明自动闭包
  • @autoclosure会自动将20封装成闭包{20}
  • @autoclosure只支持() -> T格式的参数
  • @autoclosure并非只支持最后1个参数
  • 有@autoclosure和无@autoclosure构成了函数重载
  • 空合并运算符??使用了@autoclosure技术

先分析为什么会有自动闭包,自动闭包能实现什么作用
先看下下面的一个函数

func getFirstPositive(_ a: Int, _ b: () -> Int) -> Int {
    return a > 0 ? a : b()
}

getFirstPositive(5) {
    return 20
}

getFirstPositive函数调用时使用了一个尾随闭包,当参数满足() -> T这个格式时可以写成自动闭包,会使代码阅读起来更直观

func getFirstPositive(_ a: Int, _ b: @autoclosure () -> Int) -> Int {
    return a > 0 ? a : b()
}

getFirstPositive(1, 10)

需要注意的是闭包会有推迟执行的特点,会在函数内部调用时才会执行

func getFirstPositive(_ a: Int, _ b: @autoclosure () -> Int) -> Int {
    return a > 0 ? a : b()
}

func exec() -> Int {
    print("执行了exec")
    return 20
}

getFirstPositive(1, exec())

看getFirstPositive(1, exec())函数调用时,很容易误以为exec()就已经执行了函数exec,其实并没有,exec内部没有执行,没有输出执行了exec。这是因为闭包有延迟执行的特点,getFirstPositive函数内部因为a>0返回的结果为a的值,并没有调用到b(),所以exec函数没有执行。只有当getFirstPositive函数内部调用到了b(),exec函数才会被执行

三、闭包对变量捕获

  • 闭包可以对外部函数的变量\常量进行捕获
  • 闭包捕获时机是在函数执行完,return时再去捕获
  • 当函数里有多个闭包时,只会对变量\常量捕获一次,多个闭包对捕获的变量\常量共享
  • 闭包不会对全局变量进行捕获

下面由几份代码来说明这几个结论

//MARK: 局部变量捕获
typealias fn = (Int) -> ()
func exec() -> fn {
    var num = 0
    return {a in
        num += a
        print(num)
    }
}

let fn1 = exec()
fn1(1)
fn1(2)
fn1(3)

fn1、fn2、fn3输出的结果分别是1、3、6。
这是一个函数中返回了一个闭包,在闭包里对num进行了累加并输出结果。

1、第一次调用fn1时,num为0,0加上参数1=1
2、第二次调用fn1时,闭包里的num的值是第一次fn1里的累加结果1,1加上参数2=3
3、第三次调用fn1时,闭包里num是第二次fn1里累加的结果3,3加上参数3=6
从三次调用fn1来看,闭包里num都是保存了上次调用后num的值,这是因为闭包捕获了外部的num,并重新在堆上分配了内存,当执行let fn1 = exec()时,把闭包的内存地址给了fn1,所以每次调用fn1都是调用的同一块内存,同一个闭包,闭包里有保存中捕获后的num的内存地址,所以每次调用都是同一个num

可以把闭包想象成是一个类的实例对象,内存在堆空间,捕获的局部变量\常量就是对象的成员(存储属性),组成闭包的函数就是类内部定义的方法

typealias fn = (Int) -> ()
func exec() -> fn {
    var num = 0
    return {a in
        num += a
        print(num)
    }
}

let fn1 = exec()
fn1(1)
let fn2 = exec()
fn2(1)

将上面的代码稍微改一下,将exec分别赋值给fn1和fn2,输出的结果为1和1。这个为什么不是跟上面一样累加呢,因为exec分别赋值给了fn1和fn2,fn1和fn2指向的是两个不一样的地址,当每调用一次exec()函数,num会初始化为0

typealias fn = (Int) -> ()
func exec() -> fn {
    var num = 0
    func plus(a: Int) {
        num += a
        print(num)
    }
    num = 6

    return plus
}

let fn1 = exec()
fn1(1)

上面这份代码输出的num又是多少呢?答案是7

这还是一个局部变量捕获的问题,闭包会在函数执行完,return的时候才会去捕获num,此时num已经由0变为6,所以执行fn1(1)输出结果为7

typealias fn = (Int) -> ()
func exec() -> (fn, fn) {
    var num = 0
    func plus(a: Int) {
        num += a
        print("plus:", num)
    }
    
    func minus(a: Int) {
        num -= a
        print("minus:", num)
    }

    return (plus, minus)
}

let (p, m) = exec()
p(5)
m(4)

这份代码函数返回了一个元祖,元祖里是两个闭包,两个闭包里面都调用了num,输出的结果为:
plus: 5
minus: 1
因为当函数里有多个闭包时,只会对变量\常量捕获一次,多个闭包对捕获的变量\常量共享

因为当函数里有多个闭包时,只会对变量\常量捕获一次,多个闭包对捕获的变量\常量共享。在调用m(4)时,前面已经调用过p(5),此时num已经变为5,所以当调用m(4),输出结果为1。

四、闭包中的循环引用及解决办法

大家可以看这位大神的文章Swift中闭包的简单使用,文中有详细的解析

参考文章

Swift 闭包的定义和使用
Swift中闭包的简单使用
逃逸闭包、非逃逸闭包

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容