如何使用 Swift @autoclosure

Posted

技术标签:

【中文标题】如何使用 Swift @autoclosure【英文标题】:How to use Swift @autoclosure 【发布时间】:2014-07-28 22:49:15 【问题描述】:

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

@autoclosure() -> Bool

使用重载方法返回泛型T 值,以通过LogicValue protocol 测试存在。

但严格按照手头的问题。它似乎想要一个返回 Bool@autoclosure

编写一个不带参数并返回 Bool 的实际闭包不起作用,它希望我调用闭包以使其编译,如下所示:

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

但是简单地传递一个 Bool 就可以了:

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

那么发生了什么?什么是@autoclosure

编辑: @auto_closure 更名为 @autoclosure

【问题讨论】:

【参考方案1】:

@autoclosure

@autoclosure 是一个函数参数,它接受一个熟函数(或返回类型),而一般的closure 接受一个原始函数

@autoclosure 参数类型参数必须是 '()'
@autoclosure()
@autoclosure 接受任何只返回适当类型的函数 关闭结果按需求计算

我们来看一个例子

func testClosures() 

    //closures
    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)
    ))
    
    //@autoclosure
    XCTAssertEqual("fooWithAutoClosure HelloWorld", fooWithAutoClosure(a: "HelloWorld"))
    
    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)))



//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()

【讨论】:

【参考方案2】:

这只是在闭包调用中摆脱花括号的一种方法,简单示例:

    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

【讨论】:

【参考方案3】:

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

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

所以它只使用一个表达式而不需要将它包装在一个闭包中。

【讨论】:

其实最后一个,不行。应该是f(2 >1()) @JoelFischer 我看到的和@JackyBoy 一样。致电f(2 > 1) 有效。调用 f(2 > 1) 失败并返回 error: function produces expected type 'Bool'; did you mean to call it with '()'?。我在操场上使用 Swift REPL 对其进行了测试。 我以某种方式读到倒数第二个答案作为最后一个答案,我必须仔细检查,但如果它失败了,这将是有道理的,因为你基本上是把一个闭包放在一个闭包中,从我的理解。 有一篇关于他们这样做的原因的博客文章developer.apple.com/swift/blog/?id=4 很好的解释。另请注意,在 Swift 1.2 中,“自动关闭”现在是参数声明的一个属性,所以它是 func f(@autoclosure pred: () -> Bool)【参考方案4】:

这是一个实际示例——我的 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 版本,它不会在发布模式下评估其参数。

【讨论】:

我迟到了,但是评估myExpensiveFunction()有什么影响?。如果不使用自动关闭,而是将函数传递给像print(myExpensiveFunction) 这样打印,会有什么影响?谢谢。【参考方案5】:

这显示了@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()

【讨论】:

【参考方案6】:

文档中对 auto_closure 的描述:

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

这是苹果使用的示例。

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 传递给方法,因为它是一个布尔表达式,但不能传递闭包。

【讨论】:

请注意,您实际上不需要在这里使用@auto_closure。没有它,代码可以正常工作:func simpleAssert(condition: Bool, message: String) if !condition println(message) 。当您需要重复评估参数时使用@auto_closure(例如,如果您正在实现while-like 函数)或者您需要延迟评估参数(例如,如果您正在实现短路&amp;&amp;) . @nathan 嗨,内森。您能否引用一个关于使用autoclosure 和类似while 的功能的示例?我似乎不明白这一点。非常感谢。 @connor 你可能想更新你对 Swift 3 的回答。

以上是关于如何使用 Swift @autoclosure的主要内容,如果未能解决你的问题,请参考以下文章

Swift3.0-closure的@autoclosure和@escaping

Swift开发第五篇——四个知识点(Struct Mutable方法&Tuple&autoclosure&Optional Chain)

Swift语法注意点

@autoclosure-可以让表达式自动封装成一个闭包

iOS开发-开发总结

莫名其妙的标记之@noescape