从头开始Swift:关闭

如果您使用过C或Objective-C中的块,或者使用过Ruby中的lambda,那么您将不费吹灰之力来处理闭包的概念。 闭包不过是您可以在代码中传递的功能块。

实际上,在上一课中我们已经处理了闭包。 没错:函数也是闭包。 让我们从基础开始,并检查闭合的解剖结构。

1.什么是封闭?

正如我所说,闭包是您可以在代码中传递的功能块。 您可以将闭包作为函数的参数传递,也可以将其存储为对象的属性。 闭包有很多用例。

闭包这个名字暗示了闭包的主要特征之一。 闭包捕获定义它的上下文的变量和常量。 有时将其称为“ 封闭那些变量和常量”。 在本课程结束时,我们将更详细地讨论价值获取。

灵活性

您已经了解到,功能可以非常强大和灵活。 因为函数是闭包,所以闭包同样灵活。 在本文中,您将发现它们的灵活性和强大性。

内存管理

C编程语言具有类似的概念,即block 。 但是,Swift中的闭包有一些好处。 Swift中闭包的主要优点之一是,内存管理是您(开发人员)不必担心的。

即使是保留循环 (在C或Objective-C中并不罕见)也由Swift处理。 这样可以减少由无效指针引起的难以发现的内存泄漏或崩溃。

2.语法

闭包的基本语法并不难,它可能使您想起全局和嵌套函数,这在本系列的前面已经介绍过。 看下面的例子。

{(a: Int) -> Int in
    return a + 1
}

您注意到的第一件事是,整个封口包裹在一对花括号中。 闭包的参数用一对括号括起来,用->符号与返回类型分开。 上面的闭包接受一个Int类型的参数a ,并返回一个Int 。 闭包的主体在in关键字之后开始。

命名闭包(即全局函数和嵌套函数)看起来有些不同。 以下示例应说明差异。

func increment(_ a: Int) -> Int {
    return a + 1
}

最显着的区别是func关键字的使用以及参数和返回类型的位置。 闭包以大括号开头和结尾,并包装参数,返回类型和闭包主体。 尽管存在这些差异,但请记住每个函数都是闭包。 但是,并非每个闭包都是一个函数。

3.闭包作为参数

封闭功能强大,下面的示例说明了它们的有用性。 在示例中,我们创建一个状态数组。 我们在数组上调用map(_:)函数以创建一个仅包含每个状态的前两个字母作为大写字符串的新数组。

var states = ["California", "New York", "Texas", "Alaska"]

let abbreviatedStates = states.map({ (state: String) -> String in
    let index = state.index(state.startIndex, offsetBy: 2)
    return state.substring(to: index).uppercased()
})

print(abbreviatedStates)

map(_:)函数或方法是许多编程语言和库(例如Ruby,PHP和JavaScript)所共有的。 在上面的示例中,在states数组上调用map(_:)函数,转换其内容,然后返回包含转换后的值的新数组。 现在不用担心封闭的主体。

类型推断

在本系列之前,我们了解到Swift非常聪明。 让我确切地告诉你如何聪明。 状态数组是字符串数组。 因为我们在数组上调用了map(_:)函数,所以Swift知道state参数的类型为String 。 这意味着我们可以省略类型,如下面的更新示例所示。

let abbreviatedStates = states.map({ (state) -> String in
    let index = state.index(state.startIndex, offsetBy: 2)
    return state.substring(to: index).uppercased()
})

上面的示例中我们可以省略其他一些内容,从而形成了以下一行。

let abbreviatedStates = states.map({ state in state.substring(to: state.index(state.startIndex, offsetBy: 2)).uppercased() })

让我解释一下发生了什么。

编译器可以推断出我们从传递给map(_:)函数的闭包中返回了一个字符串,这意味着没有理由将其包含在闭包表达式定义中。

但是,只有在闭包的主体包含单个语句的情况下,我们才能执行此操作。 在这种情况下,我们可以将该语句与闭包的定义放在同一行,如上例所示。 因为定义中没有返回类型,并且在返回类型之前没有->符号,所以我们可以省略括号,将闭包的参数括起来。

速记参数名称

但是,它并不仅限于此。 我们可以使用速记参数名称来进一步简化上述闭包表达式。 当使用内联闭包表达式时,如上例所示,我们可以省略参数列表,包括将参数与闭包主体分开的in关键字。

在闭包主体中,我们使用Swift提供的速记参数名称引用参数。 第一个参数由$0引用,第二个参数由$1等等。

在下面的更新示例中,我省略了参数列表和in关键字,并将闭包主体中的state参数替换为速记参数名称$0 。 所得到的语句更加简洁,而不会影响可读性。

let abbreviatedStates = states.map({ $0.substring(to: $0.index($0.startIndex, offsetBy: 2)).uppercased() })

尾随闭包

Swift编程语言还定义了一个概念,称为尾随闭包。 这个想法很简单。 如果将闭包作为函数的最后一个参数传递,则可以将该闭包放在函数调用的括号之外。 下面的示例演示了它是如何工作的。

let abbreviatedStates = states.map() { $0.substring(to: $0.index($0.startIndex, offsetBy: 2)).uppercased() }

如果函数调用的唯一参数是闭包,那么甚至可以省略函数调用的括号。

let abbreviatedStates = states.map { $0.substring(to: $0.index($0.startIndex, offsetBy: 2)).uppercased() }

请注意,这也适用于包含多个语句的闭包。 实际上,这就是Swift中可以使用尾随闭包的主要原因。 如果闭包很长或很复杂,并且是函数的最后一个参数,则通常最好使用尾随闭包语法。

let abbreviatedStates = states.map { (state) -> String in
    let index = state.index(state.startIndex, offsetBy: 2)
    return state.substring(to: index).uppercased()
}

4.捕捉价值

使用闭包时,您经常会发现自己正在使用或操纵闭包主体中闭包周围环境中的常量和变量。 这通常被称为价值捕获。 它只是意味着闭包可以从定义它的上下文中捕获常量和变量的值。 以下面的例子更好地理解价值获取的概念。

func changeCase(uppercase: Bool, ofStrings strings: String...) -> [String] {
    var newStrings = [String]()

    func changeToUppercase() {
        for s in strings {
            newStrings.append(s.uppercased())
        }
    }

    func changeToLowerCase() {
        for s in strings {
            newStrings.append(s.lowercased())
        }
    }

    if uppercase {
        changeToUppercase()
    } else {
        changeToLowerCase()
    }

    return newStrings
}

let uppercasedStates = changeCase(uppercase: true, ofStrings: "California", "New York")
let lowercasedStates = changeCase(uppercase: false, ofStrings: "California", "New York")

我确信您同意上面的示例有些人为,但是它清楚地显示了Swift中价值获取的工作原理。 嵌套函数changeToUppercase()changeToLowercase()可以访问外部函数的参数, states以及在外部函数中声明的newStates变量。

让我解释一下会发生什么。

changeCase(uppercase:ofStrings:)函数将布尔值作为第一个参数,并将类型为String的可变参数作为第二个参数。 该函数返回一个字符串数组,该字符串数组由作为第二个参数传递给该函数的字符串组成。 在函数的主体中,我们创建一个可变的字符串数组newStrings ,在其中存储修改后的字符串。

嵌套函数遍历传递给changeCase(uppercase:ofStrings:)函数的字符串,并更改每个字符串的大小写。 如您所见,它们可以直接访问传递给changeCase(uppercase:ofStrings:)函数的newStrings以及changeCase(uppercase:ofStrings:)数组,该数组在changeCase(uppercase:ofStrings:)函数的主体中声明。

我们检查uppercase的值,调用适当的函数,并返回newStrings数组。 示例末尾的两行说明了changeCase(uppercase:ofStrings:)函数的工作方式。

即使我已经演示了使用函数实现价值捕获,但请记住,每个函数都是闭包。 换句话说,相同的规则适用于未命名的闭包。

关闭

本文多次提到:函数是闭包。 共有三种关闭方式:

  • 全局功能
  • 嵌套函数
  • 闭包表达式

全局函数,例如Swift标准库的print(_:separator:terminator:)函数,不会捕获任何值。 但是,嵌套函数可以访问并可以捕获常量的值以及定义它们的函数的值。上一个示例说明了此概念。

闭包表达式也称为未命名的闭包,可以捕获定义它们的上下文中的常量和变量的值。这与嵌套函数非常相似。

复制和引用

捕获变量值的闭包能够更改该变量的值。 Swift足够聪明,可以知道它应该复制还是引用其捕获的常量和变量的值。

学习Swift且几乎没有其他编程语言经验的开发人员将把这种行为视为理所当然。 但是,Swift了解一个闭包如何使用捕获的值并因此可以为我们处理内存管理是一个重要的优势。

结论

闭包是一个重要的概念,您将在Swift中经常使用它们。 它们使您能够编写易于编写和理解的灵活,动态的代码。

在下一篇文章中,我们将从对象,结构和类开始探讨Swift中的面向对象编程。

如果您想学习如何使用Swift 3为实际应用编写高级功能代码,请查看我们的课程“借助Swift进一步学习:动画,网络和自定义控件” 。 跟着MarkusMühlberger一起学习,他用实时天气数据,自定义UI组件和一些精美的动画编写了一个功能强大的iOS天气应用程序,使一切栩栩如生。

翻译自: https://code.tutsplus.com/tutorials/swift-from-scratch-closures--cms-23138

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值