Swift函数与闭包高级用法
一、Swift 函数的高级特性
1.1 函数类型作为参数
在 Swift 中,函数也有类型。函数类型由参数类型和返回类型组成。例如,一个接受两个 Int
类型参数并返回一个 Int
类型结果的函数,其类型为 (Int, Int) -> Int
。
这种特性使得我们可以将函数作为参数传递给其他函数。下面是一个简单的示例:
// 定义一个简单的加法函数
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
// 定义一个函数,它接受一个函数类型的参数,并调用这个函数
func operate(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
// 使用 operate 函数,传递 add 函数作为参数
let result = operate(3, 5, operation: add)
print(result) // 输出 8
在上述代码中,operate
函数接受两个 Int
类型的参数 a
和 b
,以及一个函数类型的参数 operation
。operation
函数接受两个 Int
类型参数并返回一个 Int
类型结果。通过将 add
函数传递给 operate
函数,operate
函数可以调用 add
函数并返回结果。
1.2 函数类型作为返回值
不仅可以将函数作为参数传递,还可以将函数作为返回值。这在实现一些复杂的逻辑控制或延迟执行的场景中非常有用。
// 定义一个根据条件返回不同函数的函数
func chooseOperation(isAddition: Bool) -> (Int, Int) -> Int {
if isAddition {
return { a, b in a + b }
} else {
return { a, b in a - b }
}
}
// 获取加法函数
let additionFunction = chooseOperation(isAddition: true)
let addResult = additionFunction(4, 2)
print(addResult) // 输出 6
// 获取减法函数
let subtractionFunction = chooseOperation(isAddition: false)
let subtractResult = subtractionFunction(4, 2)
print(subtractResult) // 输出 2
在这个例子中,chooseOperation
函数根据 isAddition
参数的值返回不同的函数。如果 isAddition
为 true
,则返回一个加法闭包;否则返回一个减法闭包。通过这种方式,可以根据运行时的条件动态地选择要执行的操作。
1.3 嵌套函数
Swift 允许在函数内部定义其他函数,这些内部函数称为嵌套函数。嵌套函数在外部函数的作用域内,对外界是隐藏的,这有助于封装一些只在特定函数内部使用的逻辑。
func outerFunction() {
func innerFunction() {
print("这是内部函数")
}
innerFunction()
}
outerFunction() // 输出 "这是内部函数"
在上述代码中,innerFunction
定义在 outerFunction
内部,只能在 outerFunction
内部调用。这种封装可以提高代码的模块化和安全性,避免命名冲突。
1.4 可变参数
可变参数允许函数接受零个或多个指定类型的参数。在函数定义中,通过在参数类型后面加上 ...
来表示可变参数。
func sumOfNumbers(_ numbers: Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
let total = sumOfNumbers(1, 2, 3, 4, 5)
print(total) // 输出 15
在 sumOfNumbers
函数中,numbers
是一个可变参数,它可以接受任意数量的 Int
类型参数。在函数内部,numbers
被视为一个数组,可以通过遍历数组来计算总和。
1.5 输入输出参数
默认情况下,函数参数是常量。如果需要在函数内部修改参数的值并使其在函数调用后仍然有效,可以使用输入输出参数。在参数定义前加上 inout
关键字来表示输入输出参数。
func multiplyByTwo(_ number: inout Int) {
number *= 2
}
var myNumber = 5
multiplyByTwo(&myNumber)
print(myNumber) // 输出 10
在 multiplyByTwo
函数中,number
是一个输入输出参数。在调用函数时,需要在参数前加上 &
符号,表示传递的是变量的引用而不是值的副本。这样,函数内部对 number
的修改会反映到外部的变量 myNumber
上。
二、Swift 闭包的高级用法
2.1 闭包表达式
闭包表达式是一种简洁的创建闭包的方式。它的语法形式如下:
{ (parameters) -> returnType in statements }
下面是一个使用闭包表达式作为排序函数的示例:
var numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
numbers.sort { (a, b) -> Bool in
return a < b
}
print(numbers) // 输出 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
在 sort
函数的调用中,传递了一个闭包表达式作为参数。这个闭包表达式接受两个 Int
类型的参数 a
和 b
,并返回一个 Bool
类型的值,用于指示 a
是否应该排在 b
之前。
2.2 尾随闭包
当闭包表达式作为函数的最后一个参数时,可以使用尾随闭包的语法,将闭包写在函数调用的括号外面,使代码更加清晰易读。
let sortedNumbers = numbers.sorted { $0 < $1 }
print(sortedNumbers) // 输出 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
在这个例子中,sorted
函数的闭包参数写在了括号外面,使用了更简洁的尾随闭包语法。
2.3 捕获值
闭包可以捕获其定义时所在作用域中的常量和变量。即使这些常量和变量在闭包定义后离开了作用域,闭包仍然可以访问和修改它们的值。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen()) // 输出 10
print(incrementByTen()) // 输出 20
在 makeIncrementer
函数中,incrementer
闭包捕获了 runningTotal
和 amount
。每次调用 incrementByTen
时,runningTotal
的值都会被更新并保持,尽管 runningTotal
定义在 makeIncrementer
函数内部,但由于闭包的捕获机制,它的状态得以延续。
2.4 自动闭包
自动闭包是一种自动创建的闭包,它不接受任何参数,返回一个特定类型的值。通常用于延迟执行代码,直到真正需要结果时才执行。
func logIfTrue(_ condition: @autoclosure () -> Bool) {
if condition() {
print("条件为真")
}
}
let expensiveComputation: () -> Bool = {
print("执行昂贵的计算")
return true
}
logIfTrue(expensiveComputation())
// 输出 "执行昂贵的计算" "条件为真"
logIfTrue(expensiveComputation)
// 只有在条件判断时才输出 "执行昂贵的计算" "条件为真"
在 logIfTrue
函数中,condition
参数是一个自动闭包。在第一种调用方式中,expensiveComputation
被立即执行并传递结果。而在第二种调用方式中,由于 condition
是自动闭包,expensiveComputation
的执行被延迟到 if
条件判断时。
2.5 逃逸闭包
逃逸闭包是指闭包在函数返回后才被调用。如果一个闭包作为参数传递给函数,并且在函数返回后被调用,那么这个闭包就是逃逸闭包。在函数参数定义中,使用 @escaping
关键字来标记逃逸闭包。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
let someString = "Hello, world!"
someFunctionWithEscapingClosure {
print(someString)
}
someFunctionWithNonescapingClosure {
print(someString)
}
completionHandlers.first?() // 输出 "Hello, world!"
在 someFunctionWithEscapingClosure
函数中,completionHandler
是一个逃逸闭包,它被添加到 completionHandlers
数组中,在函数返回后仍然可以被调用。而在 someFunctionWithNonescapingClosure
函数中,closure
是一个非逃逸闭包,它在函数内部被立即调用。
2.6 闭包的内存管理
闭包可能会导致循环引用,从而引发内存泄漏。例如,当一个类的实例持有一个闭包,而这个闭包又捕获了该类的实例时,就会形成循环引用。
class SomeClass {
let someValue = 42
var closure: (() -> Void)?
deinit {
print("SomeClass 实例被销毁")
}
}
var instance: SomeClass? = SomeClass()
instance?.closure = {
print(instance?.someValue ?? 0)
}
instance = nil
// 这里由于循环引用,SomeClass 实例不会被销毁,不会输出 "SomeClass 实例被销毁"
为了打破这种循环引用,可以使用 weak
或 unowned
关键字来捕获实例。
class SomeClass {
let someValue = 42
var closure: (() -> Void)?
deinit {
print("SomeClass 实例被销毁")
}
}
var instance: SomeClass? = SomeClass()
instance?.closure = { [weak instance] in
guard let strongInstance = instance else { return }
print(strongInstance.someValue)
}
instance = nil
// 这里使用 weak 捕获实例,打破了循环引用,SomeClass 实例会被销毁,输出 "SomeClass 实例被销毁"
在上述代码中,使用 [weak instance]
来捕获 instance
,使得闭包对 instance
持有弱引用,从而打破了循环引用。当 instance
被设置为 nil
时,SomeClass
实例会被正确销毁。
三、函数与闭包的结合使用
3.1 用闭包实现回调
在许多异步操作中,闭包常被用作回调函数。例如,在使用 DispatchQueue
进行异步任务时,可以传递一个闭包作为任务完成后的回调。
DispatchQueue.global().async {
// 执行一些耗时操作
let result = 2 + 3
DispatchQueue.main.async {
// 回到主线程更新 UI
print("结果是 \(result)")
}
}
在这个例子中,首先在全局队列中异步执行一个任务(计算 2 + 3
),然后在主线程队列中异步执行一个闭包,用于更新 UI 或处理结果。这里的闭包作为回调函数,确保在异步任务完成后执行特定的操作。
3.2 函数柯里化
函数柯里化是将一个多参数函数转换为一系列单参数函数的过程。在 Swift 中,可以通过闭包来实现函数柯里化。
func add(a: Int) -> (Int) -> Int {
return { b in
return a + b
}
}
let addFive = add(a: 5)
let result = addFive(3)
print(result) // 输出 8
在上述代码中,add
函数接受一个 Int
类型的参数 a
,并返回一个闭包。这个闭包又接受一个 Int
类型的参数 b
,并返回 a + b
的结果。通过调用 add(a: 5)
得到一个新的函数 addFive
,它固定了第一个参数为 5
,可以直接传入第二个参数进行计算。
3.3 高阶函数与闭包组合
高阶函数是指接受其他函数作为参数或返回函数的函数。结合闭包,可以实现复杂的功能组合。
func compose<Input, Intermediate, Output>(_ f: @escaping (Intermediate) -> Output, _ g: @escaping (Input) -> Intermediate) -> (Input) -> Output {
return { input in
return f(g(input))
}
}
func square(_ number: Int) -> Int {
return number * number
}
func addOne(_ number: Int) -> Int {
return number + 1
}
let addOneAndSquare = compose(square, addOne)
let resultValue = addOneAndSquare(3)
print(resultValue) // 输出 16,先执行 addOne(3) 得到 4,再执行 square(4) 得到 16
在这个例子中,compose
是一个高阶函数,它接受两个闭包 f
和 g
,并返回一个新的闭包。这个新闭包先调用 g
函数处理输入值,再将结果传递给 f
函数进行处理。通过这种方式,可以组合不同的函数逻辑,实现更灵活和强大的功能。
3.4 闭包作为属性
类或结构体可以将闭包作为属性来存储。这在需要根据不同的情况动态改变行为时非常有用。
class MyClass {
var action: (() -> Void)?
func performAction() {
action?()
}
}
let myObject = MyClass()
myObject.action = {
print("执行自定义操作")
}
myObject.performAction() // 输出 "执行自定义操作"
在 MyClass
类中,action
是一个闭包类型的属性。可以在运行时为 action
属性赋值不同的闭包,然后通过调用 performAction
方法来执行相应的操作。这种方式使得对象的行为可以在运行时动态改变。
3.5 闭包与协议
在 Swift 中,协议可以定义闭包类型的属性或方法。这为不同类型之间的行为一致性提供了一种方式。
protocol Actionable {
var performAction: () -> Void { get }
}
class FirstClass: Actionable {
var performAction: () -> Void = {
print("FirstClass 的操作")
}
}
class SecondClass: Actionable {
var performAction: () -> Void = {
print("SecondClass 的操作")
}
}
let firstInstance = FirstClass()
let secondInstance = SecondClass()
let instances: [Actionable] = [firstInstance, secondInstance]
for instance in instances {
instance.performAction()
}
// 输出 "FirstClass 的操作" "SecondClass 的操作"
在这个例子中,Actionable
协议定义了一个 performAction
闭包类型的属性。FirstClass
和 SecondClass
类都遵循这个协议,并为 performAction
属性提供了不同的实现。通过将不同类型的实例放入一个数组中,并遍历调用 performAction
,可以实现统一的行为调用,尽管这些实例的具体类型不同。
通过深入理解和掌握 Swift 函数与闭包的这些高级用法,开发者可以编写出更加灵活、高效和可维护的代码。无论是在构建复杂的应用逻辑,还是处理异步任务和实现设计模式,这些特性都能发挥重要的作用。在实际开发中,需要根据具体的需求和场景,合理地运用函数和闭包的各种高级特性,以提升代码的质量和性能。