Swift闭包
Swift闭包

Swift闭包

闭包(Closure) 是一种能够捕获和存储其所在上下文中的变量和常量的独立代码块。换句话说,闭包可以在定义时捕获其作用域内的变量,并在闭包的生命周期内使用和修改这些变量。

闭包语法

闭包表达式的语法一般为:

{ (参数列表) -> 返回类型 in
    // 闭包的代码
}

参数函数(传入值)和返回类型(返回值),in分隔参数和闭包体。

闭包还通常配合 () -> Void 函数类型使用,例如:

func perform(action: () -> Void) {
    action()
}

() -> Void 表示参数是一个函数类型,可以传入普通函数,也可以传入闭包:

func foo() { print("foo") }

perform(action: foo)           // 普通函数
perform(action: { print("bar") }) // 闭包字面量

	当返回闭包时,也会使用 () -> Void 函数类型:
func makeLogger() -> () -> Void {
    return {
        print("log")
    }
}

闭包类型

1、全局函数(Global Functions)

全局函数是定义在全局范围内的函数,有名称,并且不会捕获任何值。

func add(a: Int, b: Int) -> Int {
    return a + b
}

2、嵌套函数(Nested Functions)

定义在函数内部,可以捕获并存储外层函数中的变量。

func outer() -> () -> Void {
    let message = "Hello"
    func inner() {
        print(message)
    }
    return inner
}
let closure = outer()
closure()  // 输出 "Hello"

3、闭包表达式(Closure Expressions)

闭包表达式可以捕获上下文中的变量,适用于需要传递函数作为参数的场景。

let addClosure: (Int, Int) -> Int = { (a, b) in
    return a + b
}
print(addClosure(2, 3))  // 输出 5

注:闭包表达式的参数括号是可选的。

上面的闭包表达式等价于:

func addClosure(a: Int, b: Int) -> Int {
    return a + b
}
print(addClosure(2, 3))  // 输出: 5

闭包语法简化

Swift可以通过类型推断和尾随闭包,简化闭包写法:

let addClosure: (Int, Int) -> Int = { $0 + $1 }

$0 和 $1 是第一个、第二个参数的建成。

如果闭包是函数最后一个参数,可以使用尾随闭包:

func performTask(completion: () -> Void) {
    completion()
}

performTask {
    print("任务完成")
}

如果不使用尾随闭包,默认写法需要显式参数名称:

performTask(completion: {
    print("任务完成")
})

闭包捕获变量

闭包可以捕获外部作用域中的变量:

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

let incrementBy2 = makeIncrementer(amount: 2)
print(incrementBy2())  // 2
print(incrementBy2())  // 4

makeIncrementer方法在传入数字2后,返回一个闭包:

{
    total += amount
    return total
}

这个闭包被incrementBy2持有(强引用)。闭包捕获了makeIncrementer方法中的total变量。

注意:这里闭包捕获的不是变量的值,而是变量存储位置的引用。

可以理解为,原本total只是makeIncrementer方法中的属性,方法结束后,属性会被移除,但是返回的闭包捕获了total变量。

当方法结束后,makeIncrementer会在函数返回后被销毁,而totalb变量会因为被闭包捕获,从栈内存提升到堆内存,闭包持有对这块堆内存的引用。

只要闭包存活,total就会存活。每次调用闭包,都会操作同一个total。

这个闭包的生命周期由incrementBy2管理,当incrementBy2所在的视图/闭包被释放的时候,incrementBy2被销毁,对应的闭包也会被销毁。

或者,如果incrementBy2是可选类型:

@State private var incrementer: (() -> Int)?
incrementer = makeIncrementer(amount: 2)

当incrementBy2为nil时,强引用断开,闭包释放。

这部分的引用知识,深入理解请见《Swift ARC自动引用计数》和《Swift深入理解闭包捕获机制》。

逃逸闭包和非逃逸闭包

闭包默认为非逃逸(Non-escaping)闭包,只能在函数返回前调用。

func foo(block: () -> Void) {
    block()
}

当闭包被异步执行或被变量保存时,就会变成逃逸闭包

var savedClosure: (() -> Void)?

func saveClosure(block: @escaping () -> Void) {
    savedClosure = block
}

需要添加 @escaping 修饰符,用来标记该闭包可能会逃出函数。

当访问实例成员时,需要显式添加 self. ,防止引用循环。

总结

Swift闭包可以作为参数传入,也可以作为返回值返回,还可以持有并修改作用域内的变量,即使作用域已经销毁,闭包仍然可以访问这些变量。

既可以捕获外部变量,也可以存储到变量(逃逸闭包)中。

相关文章

1、Swift异步

2、Swift逃逸闭包@escaping

3、Swift深入理解闭包捕获机制

4、Swift深入理解闭包表达式

5、Swift ARC自动引用计数

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注