swift4之闭包

什么是闭包?

闭包是自包含的函数代码块,可以捕获和存储其所在上下文中任意常量和变量的引用。

  • 闭包基础
  • 尾随闭包
  • 值捕获
  • 逃逸闭包与非逃逸闭包
  • 闭包的循环引用

闭包基础

闭包表达式标准格式:

{ (parameters) -> returnType in 
    // 闭包函数体
}

创建一个标准格式的闭包sum
let sum:(Int, Int) -> Int = {(a: Int, b: Int) -> Int in
    return a + b
}
可以省略声明部分的参数和返回值
let sum = { (a: Int, b: Int) -> Int in 
    return a + b
}

来试着简化闭包的写法

1. 隐式返回单表达式闭包
// 对于单行表达式闭包, 可以省略'return'关键字隐式返回单行表达式的结果
let sum = { (a: Int, b: Int) -> Int in 
    a + b
}
// 在闭包函数体部分非常短的情况下,可以将代码改为一行
let sum = { (a: Int, b: Int) -> Int in  a + b }

2. 省略参数以及返回值类型
// swift可以自动推断参数及返回值的类型,可以省略参数以及返回值类型
let sum = { (a, b) -> Int in 
    a + b 
}

let sum = { (a: Int, b: Int)  in 
    a + b 
}

let sum:(Int, Int) -> Int = { (a, b) in 
    a + b 
}

注意,如果在声明闭包的时候省略了闭包的参数类型和返回值类型,那么对于'+'操作符, 因为既适用于整型,浮点型之间的运算,
也适用于字符串之间的拼接,系统无法推断参数的类型,所以这时只能省略参数类型和返回值类型中的一个, 留下一个用来确定类型

3. 参数名称缩写
/**swift 为闭包提供了参数名称自动缩写功能,可以通过$0,$1顺序调用闭包的参数
 * 比如 第一个参数为a, 可以通过$0调用,第二个参数为b,可以通过$1调用
 * 如果使用参数名称缩写,可以在闭包表达式中省略参数列表,in 关键字也可以省略
 */
 let sum:(Int, Int) -> Int = {$0 + $1}


尾随闭包


1. 尾随闭包是一个写在函数括号之后的闭包表达式
2. 当你需要将一个很长的闭包表达式当做函数的最后一个参数,可以使用尾随闭包来增强代码的可读性
优点:使用尾随闭包可以将闭包的具体功能优雅地封装在函数后,而不需要将整个闭包包裹在函数参数括号内

下面笔者用一个sum函数对尾随闭包进行说明
1. 函数除闭包参数外还有其他参数,闭包有参数,无返回值
func sum(num1: Int, num2: Int, resultBlock :(Int) -> Void) {
    resultBlock(num1 + num2)
}

// 使用尾随闭包调用函数
sum(num1: 2, num2: 5) { (result) in
    print(result)
}
// 不使用尾随闭包调用函数
sum(num1: 2, num2: 5, resultBlock: { (result) in
    print(result)
})

2.  函数仅有一个闭包作为参数,闭包表达式无参数,无返回值
func sum(block: () -> Void) {
    block()
}

// 使用尾随闭包调用函数
sum() {
  print("尾随闭包,这是个没啥用的sum函数")
}
// 使用尾随闭包调用函数,当函数仅有一个尾随闭包作为参数,调用时可以省略括号
sum {
  print("尾随闭包,这是个没啥用的sum函数")
}
// 不使用尾随闭包调用函数
sum (block: {
 print("非尾随闭包,这是个没啥用的sum函数")
})

值捕获


闭包可以在上下文中捕获常量和变量,在自己的函数体内引用和修改这些值

/** 定义一个方法increaseOperation, 参数为整型的amount,返回值类型为() -> Int (函数类型)
 *  在方法内部定义了一个闭包increase,在increase内部捕获了result和amount
 */
func increaseOperation(_ amount: Int) -> () -> Int {
    var result = 0
    let increase: () -> Int = {
        result += amount
        return result
    }
    return increase
}

// 定义一个常量increaseByTen,该常量指向一个每次调用都会将其内部变量result加10的函数,调用多次结果如下
let increaseByTen = increaseOperation(10)
increaseByTen()   // 10 
increaseByTen()   // 20
increaseByTen()   // 30

逃逸闭包与非逃逸闭包

逃逸闭包
1. 闭包作为参数传递给一个函数
2. 闭包在函数返回之后才被执行,即闭包的生命周期长于函数
3. 可以在闭包参数名前加上@escaping关键字,指明该闭包是允许逃逸的

非逃逸闭包
1. 闭包作为参数传递给一个函数
2. 闭包在函数返回之前执行,即闭包的生命周期短于函数
3. 非逃逸的关键字是@noescape,属于默认关键字,可以省略

下面笔者用一个例子具现一下两者的区别

var message = ""
override func viewDidLoad() {
    super.viewDidLoad()
    testRequest(completionHandler: { [weak self] (success) in
        print("闭包completionHandler执行")
        self?.message = success
    }) { (error) in
        print("闭包failure执行")
        // message = error
        self.message = error
    }
}

// 函数接收了一个逃逸闭包completionHandler,一个非逃逸闭包failure
func testRequest(completionHandler: @escaping (String) -> Void, failure: (String) -> Void) {
    print("函数开始执行")
    DispatchQueue.global().async {
        print("闭包completionHandler调用")
        completionHandler("回调")
    }
    print("闭包failure调用")
    failure("失败回调")
    print("函数执行结束")
}

打印结果:
  函数开始执行
  闭包failure调用
  闭包failure执行
  函数执行结束
  闭包completionHandler调用
  闭包completionHandler执行

由打印的顺序可以看出来逃逸闭包和非逃逸闭包的区别:
逃逸闭包跳出了函数的作用域,在函数已经执行完成之后才执行回调
非逃逸闭包在函数的作用域内,执行回调结束后会再回到函数中继续往下执行

注意, 一旦使用了逃逸闭包, 就需要显式引用self, 相对的,非逃逸闭包可以隐式引用self,
但是建议不管是否逃逸闭包,都显示引用self,以提醒自己注意循环引用问题

闭包的循环引用

闭包是引用类型, 而且闭包会自动引用(持有)其内引用的所有对象,这也就是闭包及其容易造成循环引用的原因。

举个简单的例子
一个viewController, 一个customView, customView里有个闭包属性closureA
viewController.view.addSubView(customView)

他们之间的关系就是
self --> (强引用) view --> (强引用) customView --> (强引用) --> (强引用)closureA --> (强引用) self

不管self与闭包之间有0层或者100层,总之只要形成了闭环,就会出现循环引用,想要打破它也很简单,只要在其中的任意一层,造成一个弱引用即可.
一般我们在闭包中对self进行一个弱引用,用来打破引用循环

customView.closureA = { [weak self] (string: String) in
    self?.view.frame = CGRect.zero
}

你可能感兴趣的:(swift4之闭包)