Swift 闭包上|七日打卡

1,330 阅读4分钟

点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新

本文是 《Swift 100 Days》系列的的第 6 天, Swift 100 Days 是笔者记录自己 Swift 学习的记录,欢迎各位指正。

在第五天 函数 学习中,我们学会使用 func 关键字创建了一个函数。然而,Swift 中还有另一种特殊类型的函数,称为 闭包,它可以在不使用关键字 func 和函数名的情况下进行定义。

与函数一样,闭包可以接受参数和返回值。它还包含一组语句,这些语句在您调用它之后执行,并且可以作为函数分配给变量/常量。

闭包作为 Swift 中的一个复杂概念。所以我们花费两天时间来进行闭包的学习。

闭包

Closures 在 Swift 官方文档上是这么介绍的:

Closures are self-contained blocks of functionality that can be passed around and used in your code.

闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。

通俗点来说换,闭包就是一个可以分配给变量的代码块。然后可以在代码中传递它,例如传递给另一个函数。然后该函数调用闭包并执行其代码,就好像闭包是一个普通函数一样。尽管它们的工作方式类似于函数,但它们的编写方式略有不同。

在 Objective-C 我们可以使用如下方法来创建一个对象。

 NSString *string = ({
     @"你好";
 });

这是一个GNU(非标准)C语言扩展,称为语句表达式。gcc、clang和其他一些编译器支持该语法。

声明闭包

闭包表达式语法一般类似于以下形式:

{ (parameters) -> return type in
   statements
}

注意返回类型之后使用了 in 关键字。in 关键字用于分隔闭包中的语句。闭包接受参数并可以返回值。

创建一个最基本的闭包

如同函数学习一样,我们来学习创建一个无参数无返回值的最基本的闭包。

let learnSwift = {
    print("Closures are like functions")
}
learnSwift()

let learnSwift1 = { () -> Void in
    print("Closures are like functions")
}
learnSwift1()

let learnSwift2 = { () -> () in
    print("Closures are like functions")
}
learnSwift2()

上面的三个是等价的,我们可以在 Playground 中看到上面三个闭包的类型都是 () -> ()。正如我们所知,Swift 可以进行类型推断,那么我们可以显示表明对应变量/常量的类型

let learnSwift3: () -> () = {
  print("Closures are like functions")
}

闭包类型我们会在尾随闭包的时候进行详细介绍。

注意:

我理解在 {} 里实现的功能其实是一个初始化的过程,所以在使用闭包时,一定要一个接收值。并不能定义一个只初始化的功能。就像我们不能在代码的一行中写下 1

闭包参数

如同函数一样,闭包可以接收参数。我们可以按照下面的方法去定义一个接受字符串参数的闭包。

let sayHello = { (name: String) in
    print("Hello, \(name)")
}
sayHello("WWH")

let sayHello1: (String) -> () = { name in
    print("Hello, \(name)")
}
sayHello1("YHG")

let sayHello2: (String) -> () = {
    print("Hello, \($0)")
}
sayHello2("SY")

Swift 用 关键字 in 来分隔闭包参数和返回值或语句。这里我们需要注意以下几点

  • 如果我们使用类型推断来定义一个闭包的话, 我们必须要设置参数名和参数类型
  • 如果我们声明时确定了参数类型, 在{ parameter in } 中,我们可以不使用 (parameter name:parameter type) 进行定义
  • 如果我们声明时确定了参数类型,我们可以的{} 中省略参数名,并用用 $0 表示
  • 如果我们声明时确定了参数类型, 我们可以的{} 中省略参数名,可以用 _ 来代替
参数标签

与函数不同的是,在调用闭包时,不包括它的任何参数的名称,闭包是没有参数标签的。闭包所定义的参数名称其实主要是为了内部使用的。

传入多个参数

和函数一样,我们可以为闭包传入多个参数。

let sayHelloMultiple = { (name: String, age: Int, score: Double) in
    print("\(name) is \(age) year old ,score is \(score)")
}

let sayHelloMultiple1: (String,Int,Double) -> Void = { name,age,score in
    print("\(name) is \(age) year old ,score is \(score)")
}

let sayHelloMultiple2: (String,Int,Double) -> () = {
    print("\($0) is \($1) year old ,score is \($2)")
}
 
sayHelloMultiple("WWH",35,87.0)
sayHelloMultiple1("WWH",35,87.0)
sayHelloMultiple2("WWH",35,87.0)
具有可变参数的闭包
let sayHelloMore = { (name: String...) in
    print("Hello, \(name)")
}
sayHelloMore("XM","PED")

let sayHelloMore1:(String...) -> () = { (name: String...) in
    print("Hello, \(name)")
}
sayHelloMore1("WD","HDM", "XWQ")

与之前不一样的话,哪怕设置了函数类型,在{}也要定义这个参数,不然会报类型错误。

闭包返回值

参照表达式,我们可以在 ->in 关键字之间定义函数的返回值

let drivingWithReturn = { (place: String) -> String in
    return "I'm going to \(place) in my car"
}

let drivingWithReturn1:(String) -> String = {
    return "I'm going to \($0) in my car"
}

let drivingWithReturn2:(String) -> String = {
    "I'm going to \($0) in my car"
}

同函数一样,闭包的返回值可以是任意类型的。String、Int 甚至是另一个函数。

多重返回值闭包

你同样可以用元组(tuple)类型让多个值作为一个复合值从闭包中返回。

let multipleReturn = { () -> (String, Int) in
    return ("HCP", 29)
}

let user0 = multipleReturn()
print("\(user0.0),\(user0.1)")

let multipleReturn1 = { () -> (name: String, age: Int) in
    return ("HL", 19)
}

let user = multipleReturn1()
print("\(user.name),\(user.age)")
闭包的隐式返回

在 Swift 闭包处理的时候,如果闭包没有返回值,在{} 里面定义的时候,你可以隐藏 -> ()。但是注意你不可以隐藏 ()

当存在单个返回值,但是在 in 关键字后面只有一个语句时,不仅可以隐藏 -> () 也可以省略 return 关键字

let drivingWithReturn3 = { (place: String) in
    return "I'm going to \(place) in my car"
}

let drivingWithReturn4 = { (place: String) in
    "I'm going to \(place) in my car"
}

drivingWithReturn3("SuZhou")
drivingWithReturn4("SuZhou")

单个返回值的多个返回值,或者说有多个语句的时候,还是需要定义返回值类型的。

将闭包作为函数参数传递

我们可以将闭包作为函数参数在定义函数时使用。我们然后我们在函数调用时可以使用闭包。

func play(using playType: () -> Void) {
    print("Let's play a game")
    playType()
}
play(using: {
    print("Fetch!")
})

func deliverTalk(name: String, type: () -> Void) {
    print("My talk is called \(name)")
    type()
}
deliverTalk(name: "My Awesome Talk", type: {
    print("Here's a great talk!")
})

尾随闭包

如果函数的最后一个参数是闭包,Swift 允许您使用特殊的语法,称为尾随闭包语法。不是将闭包作为参数传递,而是将它直接传递到大括号内的函数之后。尾随闭包是一种特殊的将闭包作为函数参数使用。

唯一即最后

上面的两个例子我们同样可以这样子来用

play {
    print("Fetch! Trailing closure")
}

deliverTalk(name: "My Awesome Talk") {
    print("Here's a great talk! Trailing closure")
}

尾随闭包作为一种常见的 Swift 语法。需要我们好好掌握。

对比上面对于最后一个参数是闭包的函数的调用示例。我们可以看到使用尾随闭包可以让代码更加简短,可读性更好。所以出于可读性的原因,建议使用。

Xcode 中调用定义了最后一个参数是尾随闭包的参数时,会默认使用尾随闭包的样式。

在尾随闭包中传递参数

尾随闭包语法作为一种特殊的闭包用法,同样可以传递参数。

func makePizza(addToppings: (Int) -> Void) {
    print("The dough is ready.")
    print("The base is flat.")
    addToppings(3)
}
makePizza { (toppingCount: Int) in
    let toppings = ["ham", "salami", "onions", "peppers"]
    for i in 0..<toppingCount {
        let topping = toppings[i]
        print("I'm adding \(topping)")
    }
}

makePizza {
    let toppings = ["ham", "salami", "onions", "peppers"]
    for i in 0..<$0 {
        let topping = toppings[i]
        print("I'm adding \(topping)")
    }
}

同闭包或函数中使用参数一致,千万不要修改参数类型。

在尾随闭包中返回值

尾随闭包自然同样支持返回值了。

func playSong(_ name: String, notes: () -> String) {
    print("I'm going to play \(name).")
    let playedNotes = notes()
    print(playedNotes)
}
playSong("Mary Had a Little Lamb") {
    return "EDCDEEEDDDEGG"
}

尾随闭包支持返回值,闭包有一个疯狂的用法

let squared = { $0 * $0 }(12)

可不可以这样使用? 如果可以的话 squared 是什么类型?

感谢你阅读本文! 🚀