12 闭包

作者: 程序小胖 | 来源:发表于2020-01-23 20:56 被阅读0次

要说明的是,【闭包】和【闭包表达式】不是一回事,你可将【闭包表达式】看作是【闭包】的一种形式。因为闭包还有其他两种形式存在:【全局函数】和【嵌套函数】。一般的,我们所说的闭包就是闭包表达式。
总结:闭包的表现形式:【闭包表达式】、【全局函数】、【嵌套函数】

那么,下面我就说说闭包表达式:

一,闭包表达式

1. 语法
{ (参数列表) -> 返回类型 in
    实现
 }
// 简单实现
{ 实现 } 
2. 各种省略写法
// 声明一个含有闭包入参的函数
func exec(v1: Int, v2: Int, fn: (Int, Int) -> Int) {
    print(fn(v1, v2))
}

// 2.1 常规调用 in 前面是声明,后边是实现
exec(v1: 10, v2: 20, fn: {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
})
// 2.2 省略类型
exec(v1: 10, v2: 20, fn: {
    (v1, v2) -> Int in
    return v1 + v2
})
// 2.3 省略小括号()
exec(v1: 10, v2: 20, fn: {
    v1, v2 -> Int in
    return v1 + v2
})
// 2.4 省略返回值类型
exec(v1: 10, v2: 20, fn: {
    v1, v2 in
    return v1 + v2
})
// 2.5 省略return
exec(v1: 10, v2: 20, fn: {
    v1, v2 in v1 + v2
})
// 2.6 使用简化参数名 (用$0 $1... 代替参数列表)
// 表示v1和v2相加并且返回
exec(v1: 10, v2: 20, fn: { $0 + $1 })
// 为什么可以直接用$0 和 $1 代表呢? 因为在函数定义时,是有明确类型的,所以系统会自动帮忙推导

二, 尾随闭包

  • 如果将一个很长的闭包表达式,作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
  • 尾随闭包是一个被书写在函数调用括号外面的闭包表达式
  • 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的写法,那就不需要在函数后面写圆括号了
  • 一般的,开发者联想出来的就是尾随闭包的写法
// 在我们敲下exec时,系统会自动联想,然后敲回车键,此时出来的就是尾随闭包的写法
exec(v1: 20, v2: 30) { (<#Int#>, <#Int#>) -> Int in
}

// 尾随闭包使用简化参数名
exec(v1: 10, v2: 30) { $0 + $1 }

// 唯一实参,并且是尾随闭包
func closure1(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}
// 可以直接省略小括号()
closure1 { $0 + $1 }
// 可以忽略实参 _
closure1 { (_, _) -> Int in
    20
}

【注意】尾随闭包的写法,是不是似曾相识,对了,就是我们常用的网络请求

// 如果这个表达式这样写,是不是就是咋们常见的网络请求
// 请求的实现
func request(finish: (String) -> Void ) {
    finish("我是数据请求")
}

// 请求调用
request { (response) in
    print(response)
}
// 当然可以直接这样简写
request {
    print($0)
}

三,自动闭包

  • 自动闭包关键字 @autoclosure
  • 只支持 () -> T 格式的闭包
  • 空合并运算符 ?? 本质就是 自动闭包
  • 自动闭包的值,不一定执行
// 栗子
func getNum() -> Int {
    return 20
}

// 1. 简单入参
func getFirst(v1: Int, v2: Int) -> Int {
    return v1 > 0 ? v1 : v2
}
getFirst(v1: 10, v2: 20)
getFirst(v1: -1, v2: 20)
getFirst(v1: 0, v2: -1)
// 如果 v1大于0了,其实就没必要执行getNum() 了,但是实际上getFirst都会执行一遍
getFirst(v1: 20, v2: getNum())

// 2. 改造下 如何实现:当v1大于0了,就不会执行v2的代码了
// 将入参改成闭包
func getFirst2(v1: Int, v2:  () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
getFirst2(v1: -10, v2: {
    print("--------")
    return 20
})
// 尾随闭包
getFirst2(v1: 10) { () -> Int in
    print("--------")
    return 20
}

// 3. 再次改造,加入  @autoclosure 关键字
// @autoclosure 在声明时加上这个标志,可以直接传入int了
func getFirst2(v1: Int, v2: @autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
// 下面的写法不会报错
getFirst2(v1: 20, v2: 30)
// 下面这个不是简写
getFirst2(v1: 20, v2: { () -> Int in
    return 20
}())

四,逃逸闭包

  • 当一个闭包传入函数中时,在这个函数返回后才去执行闭包,那么我们就说这个闭包是逃逸的
  • 简单讲,我们常见的网络请求就是【逃逸闭包】
func reqestHomeData(finish: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        // 模拟数据加载
        
        // 数据加载完后,回调到主线程
        DispatchQueue.main.async {
            
            finish("我是异步请求数据")
        }
    }
}

func reqestHomeDataNoEscaping(finish: (String) -> Void) {
    finish("我是同步请求数据")
}

var number = 10
request { (response) in
    // 这里面需用到 number , 就必须用到 self,显示调用
    self.number = 20
    print(response)
}
reqestHomeDataNoEscaping { (response) in
    // 在非逃逸闭包里面,不需要self,隐式调用
    number = 20
    print(response)
}

五,闭包捕获值

  • 一个函数和他所捕获的变量或者常量环境组合起来,成为闭包
  • 闭包就是定义在函数内部的函数
  • 闭包捕获的是外层函数的局部变量或常量
  • 可以将闭包想象成类的实例对象
func cfn() -> () -> () {
    var a = 10
    // cfn1 就是闭包,函数里面的函数
    func cfn1() {
        // 函数里面用到了函数外层用到的局部变量量
        a = 11
    }
    return cfn1
}

// 举个例子
typealias cfn = (Int) -> Int

func getCfn() -> cfn {
    var num = 0
    // 如果这个Num是全局变量,那么也是可以用的,全局变量在全局段,全局只有一份,不会销毁
    // 但是如果是局部变量,那么在调用函数结束时,num就会被释放,在栈空间
    // 按理说,这个num应该被释放,但是为啥好像没被释放呢
    // 其实,num确实在函数调用结束后释放掉了,那么为啥还会在plus中可以使用呢,这就是闭包的捕获
    
    // 用函数表示一个闭包
    func plus(i: Int) -> Int {
        // 只在这里用到num时,申请了一块堆空间,这样就不会销毁了
        // 如果没有用到,就不会开辟
        // 其实本质上就是 将num move 到了堆空间(汇编知识)
        num += i
        return num
    }
    // 返回的plus 和 num 形成了 闭包
    return plus
}

// 获取一个函数
// fn 的内存地址中,肯定还有一份内存是存放plus的
var fn = getCfn() // 调用一次,就申请一块堆空间
print(fn(1)) // 1
print(fn(2)) // 3
print(fn(3)) // 6
print(fn(4)) // 10

var fn = getCfn() // 调用一次,num 就申请一块堆空间
var fn1 = getCfn() // 调用一次,num 就申请一块堆空间
// 总共有两份堆空间
print(fn(1)) // 1
print(fn1(2)) // 2
print(fn(3)) // 4
print(fn1(4)) // 6

// 闭包表达式表示一个闭包
func testFn() -> cfn {
    var num = 0
    // 利用闭包表达式来表示函数
    let fn = { (v1: Int) -> Int in
        num += v1
        return num
    }
    return fn
    
// 简写
//    return {
//        num += $0
//        return num
//    }
}

var testfn = testFn()
print(testfn(3))
以上就是我对闭包的理解,说实在的,我是学完语法,然后做了一段时间的项目,再回头看语法时,才稍微理解了点这个闭包,因为我们常用的就是一个尾随的逃逸闭包,其他的很少接触。没有项目经验,刚接触肯定会蒙圈,只要多写多练多思考,就好了

过年快乐

相关文章

  • Python入门与进阶(11-11)

    11-11 一个事例看看闭包11-12 闭包的经典误区

  • Swift语法--12-3闭包的循环引用

    Swift语法--12-3闭包的循环引用 闭包循环引用产生条件 如果在HttpTool中有对闭包进行强引用,则会形...

  • 12 闭包

    要说明的是,【闭包】和【闭包表达式】不是一回事,你可将【闭包表达式】看作是【闭包】的一种形式。因为闭包还有其他两种...

  • swift-闭包

    闭包 闭包定义 闭包简化 - 尾随闭包 闭包参数 闭包返回值 闭包的循环引用

  • 闭包,闭包,闭包

    1、这家伙到底是什么? 网上关于这个的讨论的太多了太多了,有各种的举例子,但是大部分还在寻找这个答案的小伙伴对于变...

  • 闭包-Closures [swift 5.1]

    闭包的语法 尾随闭包 闭包逃离 自动闭包

  • Day7 闭包(Closures)

    本页包含内容:• 闭包表达式• 尾随闭包• 值捕获• 闭包是引用类型• 逃逸闭包• 自动闭包 1、闭包表达式 闭包...

  • Python闭包

    闭包 = 环境变量 + 函数 调用闭包内部的环境变量 闭包的经典误区 闭包与非闭包实现人类走路 非闭包 闭包

  • 闭包(closure)

    ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...

  • swift- 闭包一

    /*• 闭包表达式• 尾随闭包• 值捕获• 闭包是引用类型• 逃逸闭包• 自动闭包*/

网友评论

      本文标题:12 闭包

      本文链接:https://www.haomeiwen.com/subject/mmteactx.html