如何使用 Swift@autoclose

在用 Swift 编写 assert时,我注意到第一个值的类型为

@autoclosure() -> Bool

使用重载方法返回一个通用的 T值,通过 LogicValue protocol测试是否存在。

然而,严格地坚持手头的问题。它似乎想要一个返回一个 Bool@autoclosure

编写一个实际的闭包,不接受任何参数,并返回一个 Bool,这是行不通的,它希望我调用闭包来编译它,像这样:

assert({() -> Bool in return false}(), "No user has been set", file: __FILE__, line: __LINE__)

然而,简单地传递一个布尔函数是可行的:

assert(false, "No user has been set", file: __FILE__, line: __LINE__)

到底是怎么回事? 什么是 @autoclosure

编辑: @auto_closure更名为 @autoclosure

31548 次浏览

来自文档的 auto _ close 描述:

可以将 auto _ close 属性应用于具有 参数 type of () ,并返回表达式的类型(参见 类型属性)。自动闭包函数捕获隐式闭包 而不是表达式本身 下面的示例在定义一个非常 简单断言函数:

这是苹果公司用的例子。

func simpleAssert(condition: @auto_closure () -> Bool, message: String) {
if !condition() {
println(message)
}
}
let testNumber = 5
simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.")

基本上它的意思是你传递一个布尔表达式作为第一个参数,而不是一个闭包,它会自动为你创建一个闭包。这就是为什么可以向方法传递 false,因为它是一个布尔表达式,但不能传递闭包。

考虑一个只接受一个参数的函数,一个不接受参数的简单闭包:

func f(pred: () -> Bool) {
if pred() {
print("It's true")
}
}

要调用这个函数,我们必须传入一个闭包

f(pred: {2 > 1})
// "It's true"

如果我们省略了大括号,我们就传递了一个表达式,这是一个错误:

f(pred: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

@autoclosure在表达式周围创建一个自动闭包。因此,当调用方编写像 2 > 1这样的表达式时,它会自动封装到一个闭包中,在传递给 f之前成为 {2 > 1}。如果我们把这个应用到函数 f:

func f(pred: @autoclosure () -> Bool) {
if pred() {
print("It's true")
}
}


f(pred: 2 > 1)
// It's true

所以它只用一个表达式就可以工作,而不需要用闭包包装它。

这显示了 @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/的一个有用案例

现在,作为第一个参数传递给 until 的条件表达式将自动包装成一个闭包表达式,并且可以在循环中每次调用

func until<L: LogicValue>(pred: @auto_closure ()->L, block: ()->()) {
while !pred() {
block()
}
}


// doSomething until condition becomes true
until(condition) {
doSomething()
}

这里有一个实际的例子ーー我的 print覆盖(这是 Swift 3) :

func print(_ item: @autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
#if DEBUG
Swift.print(item(), separator:separator, terminator: terminator)
#endif
}

当您说 print(myExpensiveFunction())时,我的 print覆盖使 Swift 的 print相形见绌,并被调用。因此,myExpensiveFunction()被封装在一个闭包 而不是评估中。如果我们在发布模式,它将 永远不会进行评估,因为 item()将不会被调用。因此,我们有一个版本的 print,不评估其参数在发布模式。

这只是在一个闭包调用中去掉大括号的一种方法,简单的例子:

    let nonAutoClosure = { (arg1: () -> Bool) -> Void in }
let non = nonAutoClosure( { 2 > 1} )


let autoClosure = { (arg1: @autoclosure () -> Bool) -> Void in }
var auto = autoClosure( 2 > 1 ) // notice curly braces omitted

@ autoclose

@autoclosureclosure< sup > [ About ] 的函数参数内转换(包装)表达式

优点:

  • 易读 assert(2 == 5, "failed")
  • 不使用花括号

缺点

  • 很难读懂。当在 @autoclosure中传递函数时,不清楚这个函数是否会被延迟(因为它在 @autoclosure中是闭包)。fooWithAutoClosure(a: foo0())-foo0()将不会立即被调用,因为我们期望阅读这一行

过度使用自动闭包会使代码难以理解。上下文和函数名应该清楚地表明计算被延迟了。

官方文件

  • @autoclosure不接受任何参数
    func foo(p: @autoclosure () -> Void)
    
  • @autoclosure只接受适当的 回来了类型的任何函数

更多的例子

//functions block
func foo0() -> String {
return "foo0"
}


func foo1(i1: Int) -> String {
return "foo1 " + String(i1)
}


func foo2(i1: Int, i2: Int) -> String {
return "foo2 " + String(i1 + i2)
}
//closures block
func fooWithClosure0(p: () -> String) -> String {
return "fooWithClosure0 " + p()
}


func fooWithClosure1(p: (Int) -> String) -> String {
return "fooWithClosure1 " + p(1)
}


func fooWithClosure2(p: (Int, Int) -> String) -> String {
return "fooWithClosure2 " + p(1, 2)
}
//@autoclosure
func fooWithAutoClosure(a: @autoclosure () -> String) -> String {
return "fooWithAutoClosure " + a()
}
//test closures
func testClosures() {
XCTAssertEqual("fooWithClosure0 foo0", fooWithClosure0(p: foo0))
XCTAssertEqual("fooWithClosure1 foo1 1", fooWithClosure1(p: foo1))
XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: foo2))
    

XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: { (i1, i2) -> String in
return "fooWithClosure2 " + "foo2 " + String(i1 + i2)
}))
}
//test @autoclosure
func testAutoClosures() {
XCTAssertEqual("fooWithAutoClosure HelloWorld", fooWithAutoClosure(a: "HelloWorld")) //"HelloWorld" is String as returned value of @autoclosure
    

XCTAssertEqual("fooWithAutoClosure foo0", fooWithAutoClosure(a: foo0()))
XCTAssertEqual("fooWithAutoClosure foo1 1", fooWithAutoClosure(a: foo1(i1: 1)))
XCTAssertEqual("fooWithAutoClosure foo2 3", fooWithAutoClosure(a: foo2(i1: 1, i2: 2)))
}