代码改变世界

Swift学习笔记(8):闭包

2017-05-19 20:41  杨平  阅读(429)  评论(0编辑  收藏  举报

目录:

  • 基本语法
  • 尾随闭包
  • 值捕获
  • 自动闭包

闭包是自包含的函数代码块,闭包采取如下三种形式之一:

・全局函数是一个有名字但不会捕获任何值的闭包
・嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
・闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

使用简洁的闭包表达式具有以下优点:

・利用上下文推断参数和返回值类型
・隐式返回单表达式闭包,即单表达式闭包可以省略 return 关键字
・参数名称缩写
・尾随闭包语法

 函数和闭包都是引用类型。

 

基本语法
{ (parameters) -> returnType in
    statements
}
(1). 闭包表达式参数可以是in-out参数,但不能设定默认值
(2). 闭包表达式参数可以使用具体名称的可变参数
(3). 可以使用元组作为闭包表达式的参数和返回值
// 内联闭包
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
     return s1 > s2
})

// 简写方式
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

// 内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型。
// 这意味着闭包作为函数或者方法的参数时,可以不用完整格式构造内联闭包
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

// 单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

// Swift自动为内联闭包提供参数名称缩写功能,可以使用 $0,$1,$2等来顺序调用闭包参数
reversedNames = names.sorted(by: { $0 > $1 } )

// 运算符方法,String的运算符>刚好接收两个String入参返回Bool结果,可以简写成
reversedNames = names.sorted(by: >)

 

尾随闭包

如果闭包表达式作为函数最后一个参数,可以使用书写在函数括号之外的省略闭包表达式参数标签的尾随闭包来增强函数的可读性。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 内联闭包进行函数调用 
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})

// 尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}

reversedNames = names.sorted() { $0 > $1 }

// 如果闭包表达式作为函数或方法的唯一参数,则使用尾随闭包时,可以省略()
reversedNames = names.sorted { $0 > $1 }

 

值捕获

闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

// makeIncrementer执行完后,runningTotal的作用域不存在了,
// 但调用返回的函数时依然能够改变和访问runningTotal的值
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTen = makeIncrementor(forIncrement: 10)

// 闭包是引用类型,将闭包同时赋值给两个变量或常量,这两个变量或常量指向同一个闭包
let alsoIncrementByTen = incrementByTen

 

逃逸闭包

当一个闭包作为函数参数传入,但在函数执行返回之后才被执行,称该闭包为逃逸闭包。可以使用 @escaping 在参数前标注闭包为逃逸闭包,用来指明这个闭包是允许“逃逸”出这个函数的。

异步处理操作通常需要传入一个可以逃逸的闭包作为complete callback,这时往往将传入的闭包保存在函数外部定义的变量中。

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) // 打印出 "200"
completionHandlers.first?() print(instance.x)                       // 打印出 "100"

 

自动闭包

自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式 来代替显式的闭包。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 
print(customersInLine.count)                 // 打印出 "5"
let customerProvider = { customersInLine.remove(at: 0) } 
print(customersInLine.count)                 // 打印出 "5"
print("Now serving \(customerProvider())!")  // Prints "Now serving Chris!" 
print(customersInLine.count)                 // 打印出 "4"

通过将参数标记为 @autoclosure 来接收一个自动闭包。下面serve()以自动闭包做函数参数可以视为接受String类型参数(而非闭包)的函数来调用。

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))  // 打印 "Now serving Ewa!"

如果像让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosure 和 @escaping 属性。

// customersInLine i= ["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.")  // 打印 "Collected 2 closures."
for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// 打印 "Now serving Barry!"
// 打印 "Now serving Daniella!"

 

声明:该系列内容均来自网络或电子书籍,只做学习总结!