SerendipityEx

关注成长,记录生活

Swift 闭包

根据 The Swift Programming Language (Swift 4.1) - Closures 整理。

Swift 中闭包与 Objective-C 中的 Blocks 以及其他一些编程语言中的匿名函数比较相似,是自包含的函数代码块。

闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。

Swift 会为你管理在捕获过程中涉及到的所有内存操作。

闭包有三种形式:

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

Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下

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

闭包表达式语法

{ (parameters) -> return type in
    statements
}

示例:sorted 方法

sorted(by:) 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}

var reversedNames = names.sorted(by: backward)

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 })<Paste>

单表达式闭包隐式返回

单行表达式闭包可以通过省略 return 关键字来隐式返回单行表达式的结果

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 })

参数名称缩写

Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0,$1,$2 来顺序调用闭包的参数,以此类推。

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

运算符方法

reversedNames = names.sorted(by: >)

尾随闭包

将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}
 
// 不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // closure's body goes here
})
 
// 使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // trailing closure's body goes here
}

如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉。

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

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

值捕获

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


func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runingTotal = 0
    func increment() -> Int {
        runingTotal += amount
        return runingTotal
    }
    return increment
}


let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen() // 10
incrementByTen() // 20
incrementByTen() // 30

let incrementBySevem = makeIncrementer(forIncrement: 7)

incrementBySevem() // 7
incrementByTen()   // 40

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen() // 50
incrementByTen()     // 60

闭包是引用类型

上面的例子中,incrementBySeven 和 incrementByTen 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。

无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用 incrementByTen 是一个常量,而并非闭包内容本身。

闭包引起的循环强引用

循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod()。这两种情况都导致了闭包“捕获”self,从而产生了循环强引用。

弱引用和无主引用

  • weak: 弱引用
  • unowned:无主引用

在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。

相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。 弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。

如果被捕获的引用绝对不会变为nil,应该用无主引用,而不是弱引用。

定义捕获列表

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // 这里是闭包的函数体
}

如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方:

lazy var someClosure: Void -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // 这里是闭包的函数体
}

逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。 通过在参数名之前标注 @escaping, 用来指明这个闭包是允许“逃逸”出这个函数的。

一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。

将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 self

非逃逸闭包,可以隐式引用 self

var completionHandles: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandles.append(completionHandler)
}

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

completionHandles.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())!")
// Now serving Chris!
print(customersInLine.count)
// 4
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(customer: { customersInLine.remove(at: 0) })
// Now serving Alex!

自动闭包"逃逸"

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 Ewa!
// Now serving Barry!

Ending…

使用 Automator 为文件夹添加一个快捷操作