基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

什么是 自动闭包 (@autoclosure)?

知识点图片

自动闭包 (@autoclosure) 是 Swift 中的一种语法糖(Syntactic Sugar),用于修饰函数的参数。

简单来说,它自动把你传递给函数的“普通表达式”包装成一个“闭包”

这样做主要有两个目的:

  1. 语法简洁:调用函数时不需要写花括号 {}
  2. 延迟执行(Lazy Evaluation):参数中的代码只有在函数内部真正被调用时才会执行。

1. 直观对比:没有 vs 有 @autoclosure

假设我们要写一个打印日志的函数,只有在调试模式下才打印。

普通写法 (没有 @autoclosure)

参数必须显式地传入一个闭包。

plaintext
func log(message: () -> String) {
    if isDebugging {
        print(message())
    }
}

// 调用时,必须写花括号
log(message: { "这是一个错误信息" }) 

使用 @autoclosure 的写法

参数被自动包装,调用看起来像传了一个普通字符串。

plaintext
// 在参数类型前加上 @autoclosure
func log(message: @autoclosure () -> String) {
    if isDebugging {
        print(message())
    }
}

// 调用时,省略花括号,看起来非常自然
log(message: "这是一个错误信息") 

2. 核心特性:延迟执行 (关键点)

你可能会问:“为什么不直接传 String,而要传 () -> String?”

答案是为了性能。

如果传入的是 String,那么在调用函数的那一刻,参数的值就会被立即计算。如果计算这个参数非常耗时(比如涉及复杂的数学运算或格式化),但函数内部最终决定不使用它(例如 isDebuggingfalse),那么这个计算就浪费了。

使用 @autoclosure,虽然你写的是一个表达式,但它被包装成了闭包。只有当函数体内调用 message() 时,那个表达式才会被执行。

示例:

plaintext
func complexCalculation() -> String {
    print("正在进行复杂计算...")
    return "计算结果"
}

func check(condition: Bool, value: @autoclosure () -> String) {
    if condition {
        print("条件满足: \(value())") // 只有在这里,complexCalculation 才会运行
    } else {
        print("条件不满足") // 这里 value() 没被调用,复杂计算被跳过了
    }
}

// 调用
check(condition: false, value: complexCalculation())

// 输出:
// 条件不满足
// (注意:"正在进行复杂计算..." 并没有被打印出来)

3. Swift 标准库中的应用

Swift 标准库大量使用了 @autoclosure,最典型的例子是:

A. 断言 (assert)

assert 函数只有在条件为 false 时才需要计算并打印后面的错误信息。

plaintext
// 标准库定义大概长这样:
public func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)

// 调用:
assert(x > 0, "x 必须大于 0") 
// 只有当 x > 0 为 false 时,字符串 "x 必须大于 0" 才会被创建。

B. 空合运算符 (??)

a ?? b,如果 a 有值,b 根本不会被计算(短路求值)。

plaintext
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

// 调用
let value = cachedValue ?? downloadFromNetwork()
// 如果 cachedValue 有值,downloadFromNetwork() 永远不会执行。

4. @autoclosure@escaping

默认情况下,@autoclosure非逃逸(non-escaping)的。这意味着这个闭包只能在函数作用域内执行,不能被存储起来稍后执行。

如果你需要把这个自动闭包保存到数组里,或者在异步操作中执行,你需要同时使用 @autoclosure@escaping

plaintext
var completionHandlers: [() -> Void] = []

func collect(handler: @autoclosure @escaping () -> Void) {
    completionHandlers.append(handler)
}

collect(handler: print("任务完成"))
// 此时 print 还没执行,它被存到了数组里

总结

  • 定义@autoclosure 把参数表达式自动封装成一个不接受参数的闭包。
  • 优点
    1. 代码美观:调用时省略 {}
    2. 性能优化:实现延迟求值(Lazy Evaluation),避免不必要的计算。
  • 注意事项:不要滥用。如果函数的行为本身不带有“延迟计算”的意味,滥用 @autoclosure 会让代码逻辑变得隐晦(调用者以为传的是值,结果传的是代码块),降低代码可读性。
00:00
00:00