12 闭包

2020-01-23  本文已影响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)
}

三,自动闭包

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

过年快乐

上一篇下一篇

猜你喜欢

热点阅读