美文网首页
swift学习笔记(7)--- 闭包

swift学习笔记(7)--- 闭包

作者: Rui_ai | 来源:发表于2019-10-23 19:42 被阅读0次

闭包是自包含的函数代码块,可以在代码中被传递和使用,类似于OC和C里面的Block

闭包可以捕获和存储其所在上下文中任意常量和变量的引用,被称为包裹常量和变量。Swift会为你管理在捕获过程中涉及到的所有内存操作。

全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:

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

Swift的闭包表达式拥有简洁的风格,主要优化如下:

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

1、闭包表达式

闭包表达式是一种构建内联闭包的方式,它的语法简洁,一般语法形式为:

{ (parameters) -> return type in
    statements
}

在保证不丢失它语法清晰明了的同时,闭包表达式也提供了几种优化的语法简写形式,以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 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
  • 将函数以闭包的形式实现为:
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 来顺序调用闭包的参数,in 关键字也同样可以被省略:
reversedNames = names.sorted(by: {$0 > $1})
  • Swift 的 String 类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sorted(by:) 方法的参数需要的函数类型相符合,因此,可以简单地传递一个大于号:
reversedNames = names.sorted(by: >)

注意:

  • 闭包表达式参数可以是 in-out 参数,但不能设定默认值。如果你命名了可变参数,也可以使用此可变参数。元组也可以作为参数和返回值。
  • 闭包作为函数或者方法的参数时,几乎不需要利用完整格式构造内联闭包,因为总是能够推断出闭包的参数和返回值类型。

2、尾随闭包

尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,可以不用写出它的参数标签:

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

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})

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

字符串排序闭包可以作为尾随闭包的形式改写为:

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

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

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

当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。举例来说,Swift 的 Array 类型有一个 map(_:) 方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。

当提供给数组的闭包应用于每个数组元素后,map(_:) 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
let strings = numbers.map {
    (number) -> String in
    var number = number
    var output = ""
    repeat {
        output = digitNames[number % 10]! + output
        number /= 10
    } while number > 0
    return output
}
// strings 常量被推断为字符串类型数组,即 [String]
// 其值为 ["OneSix", "FiveEight", "FiveOneZero"]

注意:字典 digitNames 下标后跟着一个叹号(!),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定 number % 10 总是 digitNames 字典的有效下标,因此叹号可以用于强制解包(force-unwrap)存储在下标的可选类型的返回值中的 String 类型的值。

3、值捕获

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

Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回的值为7
incrementByTen()
// 返回的值为40

注意:如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。

4、闭包是引用类型

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

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

这也意味着如果你将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值为50

5、逃逸闭包

逃逸闭包:当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,用 @ escaping关键字来定义。

//将闭包保存在函数外部定义的变量中
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

逃逸闭包显式地引用 self,非逃逸闭包则可以隐式引用 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”

6、自动闭包

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

我们经常会调用采用自动闭包的函数,但是很少去实现这样的函数。举个例子来说,assert(condition:message:file:line:) 函数接受自动闭包作为它的 condition 参数和 message 参数;它的 condition 参数仅会在 debug 模式下被求值,它的 message 参数仅当 condition 参数为 false 时被计算求值。

自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出“5”

//customerProvider 的类型不是 String,而是 () -> String
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出“5”

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出“4”

将闭包作为参数传递给函数时,你能获得同样的延时求值行为:

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

通过将参数标记为 @autoclosure 来接收一个自动闭包,而不是接受一个显示的闭包,也可以实现相同的效果:

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

注意:过度使用 autoclosures 会让代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延时执行的。

如果想让一个自动闭包可以“逃逸”,则应该同时使用 @autoclosures@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!”

相关文章

  • Swift学习笔记(1)

    SWift学习笔记 闭包 闭包表达式 闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 ...

  • swift学习笔记 ⑥ —— 闭包

    Swift学习笔记 - 文集 闭包,就是能够读取其他函数内部变量的函数。Swift 中的闭包与 C 和 OC 中的...

  • swift学习笔记(7)--- 闭包

    闭包是自包含的函数代码块,可以在代码中被传递和使用,类似于OC和C里面的Block。 闭包可以捕获和存储其所在上下...

  • swift闭包学习

    闭包作为参数 参考 Swift学习之闭包

  • Swift5.x-枚举(中文文档)

    引言 继续学习Swift文档,从上一章节:闭包,我们学习了Swift闭包相关的内容,如闭包的定义和使用、闭包的简写...

  • Swift学习笔记-闭包

    闭包(closure)是一种更加紧凑,轻量的匿名函数,类似于Java中的lambda表达式 swift中的闭包语法...

  • swift 闭包(学习笔记)

    全局和内嵌函数,实际上是特殊的闭包。闭包符合如下三种形式中的一种: 全局函数是一个有名字但不会捕获任何值的闭包; ...

  • Swift-闭包

    Swift 闭包 函数 ()->() Swift 中的闭包和 Objective-C 中的 block 类似,闭包...

  • Swift 2.0 学习笔记 7_闭包

    //: 闭包 //: 闭包的定义 // 闭包是自包含的函数代码块, 可以在代码中被传递和使用. 闭包可以捕获和存储...

  • Swift闭包和函数

    函数在Swift中只是一种特殊的闭包,闭包在Swift语言中是一等公民,支持闭包嵌套和闭包传递。Swift中的闭包...

网友评论

      本文标题:swift学习笔记(7)--- 闭包

      本文链接:https://www.haomeiwen.com/subject/btbbdctx.html