Swift从入门到精通第六篇 - 闭包(Closures) 初识
闭包(学习笔记)
环境Xcode 11.0 beta4 swift 5.1
-
闭包(Closures)
- 闭包有三种形式:
- 全局函数是具有名称且不捕获任何值的闭包。
- 嵌套函数是具有名称的闭包,可以从其封闭函数中捕获值。
- 闭包表达式(closure expressions)是用轻量级语法编写的未命名闭包,可以从它们周围的上下文捕获值。
- 闭包有三种形式:
-
闭包表达式(Closure Expressions)
-
Swift的闭包表达式有一个干净、清晰的风格,通过优化,在常见场景中鼓励使用简洁、整洁的语法。这些优化包括:
- 从上下文中可以推导参数和返回值
- 隐式返回值即如果只有一句语句,如果需要返回值
return
关键字可以省略 - 简写的参数名字。例如$0
- 尾随闭包语法
-
闭包表达式基本语法
{ (<#parameters#>) -> <#return type#> in <#statements#> } // in 关键字表示闭包的参数、返回值结束,接下来的是闭包体的开始
-
尾随闭包:如果闭包是函数的最后一个参数,传入的实参可以写成尾随闭包这种形式,见下面的示例代码
-
闭包表达式简单示例
// 定义一个字符串数组 let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] // 定义一个闭包表达式 func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } // 使用数自带的 sorted 方法, 此方法是接收一个闭包表达式 var reversedNames = names.sorted(by: backward) // 输出结果 // reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"] // 由于此闭包比较简单,可以直接写成内联形式,如下 reversedNames = names.sorted(by: {(s1: String, s2: String) -> Bool in return s1 > s2 }) // 由于闭包可以自动推导类型,因为传入函数的参数类型肯定是 (String, String) -> Bool ,这意味着参数类型和返回值类型已确定,可以省略,简写如下 reversedNames = names.sorted(by: {s1, s2 in return s1 > s2 }) // 只有一句表达式,还可以简写如下, return 关键字也可以省略 reversedNames = names.sorted(by: {s1, s2 in s1 > s2 }) // 如果用简写的参数 reversedNames = names.sorted(by: {$0 > $1 }) // 如果用字符串已实现的运算符 >, 还可以简写 reversedNames = names.sorted(by: > ) // 用尾随闭包 reversedNames = names.sorted(){$0 > $1} // 因为此函数只有一个参数,函数的小括号也可以省略 reversedNames = names.sorted{$0 > $1}
-
-
闭包值捕获(Capturing Values)
- 示例代码如下
func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer } let incrementByTen = makeIncrementer(forIncrement: 10) incrementByTen() // returns a value of 10 incrementByTen() // returns a value of 20 incrementByTen() // returns a value of 30 let incrementBySeven = makeIncrementer(forIncrement: 7) incrementBySeven() // returns a value of 7 incrementByTen() // returns a value of 40
- 如果您将闭包分配给类实例的属性,并且闭包通过引用实例或其成员来捕获该实例,那么您将在闭包和实例之间创建一个强引用循环
class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { if let text = self.text { return "<\(self.name)>\(text)</\(self.name)>" } else { return "<\(self.name) />" } } init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { print("\(name) is being deinitialized") } } var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) // Prints "<p>hello, world</p>" paragraph = nil // 此时 deinit方法并不会调用 // 这样会产生循环引用,见下图
// 可以使用 unowned \ weak 解决循环引用问题 class HTMLElement { let name: String let text: String? lazy var asHTML: () -> String = { [unowned self] in if let text = self.text { return "<\(self.name)>\(text)</\(self.name)>" } else { return "<\(self.name) />" } } init(name: String, text: String? = nil) { self.name = name self.text = text } deinit { print("\(name) is being deinitialized") } } var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") print(paragraph!.asHTML()) // Prints "<p>hello, world</p>" paragraph = nil // Prints "p is being deinitialized"
- 如何定义一个捕获列表,用关键字 weak or unowned 中间用 ','分割
lazy var someClosure: (Int, String) -> String = { [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in // closure body goes here }
- `unowned` 经常使用在闭包和相互引用的实例对象同时销毁的时候 - `weak` 经常使用在当捕获的引用可能在将来的某个时刻变为nil时,弱引用始终是可选的类型,当它们引用的实例被释放时,将自动变为nil。这使您能够检查它们是否存在于闭包的主体中。
- 闭包是引用类型(Closures Are Reference Types)
// 继续上面的例子,如果将 incrementByTen 将赋值给一个新的常量或者变量,示例代码如下 let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // returns a value of 50 incrementByTen() // returns a value of 60
-
逃逸闭包(Escaping Closures)
- 用关键字 @escaping 修饰
- 如下例子当一个函数参数是闭包,要存入外部一个变量,此时必须要用 @escaping 修饰
var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) }
- 如果用 @escaping 修饰,那必须显示的调用 self
func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } } let instance = SomeClass() instance.doSomething() print(instance.x) // Prints "200" completionHandlers.first?() print(instance.x) // Prints "100"
-
自动闭包(Autoclosures)
- 用标识符 @autoclosure 修饰
- 作为参数传递给函数的表达式,不接收任何参数并返回封装在表达式内的值(
() -> T
这种形式),如果只有一个数不可以省略大括号 - 自动闭包通常用在延迟计算,因为在调用闭包之前,内部的代码不会运行
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // Prints "5" let customerProvider = { customersInLine.remove(at: 0) } // 定义一个闭包表达式 () -> String print(customersInLine.count) // Prints "5" print("Now serving \(customerProvider())!") // Prints "Now serving Chris!" print(customersInLine.count) // Prints "4" // 观察以下两段代码 // customersInLine now is ["Alex", "Ewa", "Barry", "Daniella"] func serve(customer customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serve(customer: { customersInLine.remove(at: 0) } ) // Prints "Now serving Alex!" // customersInLine now is ["Ewa", "Barry", "Daniella"] // 此处用了自动闭包, {}都省略了 func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) // Prints "Now serving Ewa!"
- 可以同时使用自动闭包和逃逸闭包, 用 @autoclosure @escaping 修饰,不分先后
// customersInLine is ["Barry", "Daniella"] var customerProviders: [() -> String] = [] func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) { customerProviders.append(customerProvider) } collectCustomerProviders(customersInLine.remove(at: 0)) collectCustomerProviders(customersInLine.remove(at: 0)) print("Collected \(customerProviders.count) closures.") // Prints "Collected 2 closures." for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // Prints "Now serving Barry!" // Prints "Now serving Daniella!"