如何在Swift中使用可变参数闭包?

3

您好,以下是关于在Swift中使用可变参数列表的闭包的正确方法:

在Swift中,可以像这样声明一个接受可变参数列表的函数:

protocol NumberType: Comparable, IntegerLiteralConvertible, IntegerArithmeticType {}
extension Int: NumberType {}

extension SequenceType where Generator.Element: NumberType {
    func satisfy(closure:(args: Generator.Element...) -> ()) {
        // Do something
        closure(args: 1, 2, 3)
    }
}

构建很顺利。当我尝试使用该函数时:
[1, 2].satisfy { (args) in
    print (args)
}

Xcode可以像我期望的那样自动完成,但是在args括号关闭后立即,Xcode中的所有语法高亮都消失了,并出现了一条消息"Command failed due to signal: Segmentation Fault: 11",这似乎意味着Xcode非常困惑。

为了更好地理解,我打算看看Swift能否编写一个函数,该函数可以基于可变数量的参数返回答案(映射到获取暴力答案所需的for循环数)。 这将是一种简单的方法来测试回答问题,例如“给定一个Int数组,找到满足方程式a^3 + b^3 = c^3 + d^3的所有组合”。

let answer = array.satisfy ({ return pow($0, 3) + pow($1, 3) == pow($2, 3) + pow($3, 3) })

针对一个更优的解决方案。

"返回所有的2"只需

let answer = array.satisfy ({ return $0 == 2 })

一个for循环

我认为你正在以正确的方式进行,但是你发现了一个错误。 - nhgrif
1个回答

4

编译器限制/单表达式闭包参数类型推断错误

我认为这个问题的根源是编译器目前关于使用可变参数的单行闭包中推断参数类型的限制(/错误),请参见以下问答

  1. 为什么我不能在带有可变匿名参数的一行Swift闭包中使用.reduce()?

在Swift 2.1中也存在类似的问题,涉及到inout参数(但在2.2中已经不存在了),详见以下讨论

  1. 内联if语句改变void返回闭包中的inout参数,奇怪的错误(错误:类型'Int1'不符合协议'BooleanType')

查看线程1,并尝试在Swift JIRA中找到描述的错误,但似乎线程1的原帖作者从未为此提交过错误报告。可能我还没有找到现有的错误报告,但如果不存在,则可能需要提交一个。


当前解决方法

在编译器的闭包参数类型推断赶上之前,可能的解决方法是:

  • Extend the closure beyond single-line body

    // ...
    
    [1, 2].satisfy { (args) in
        () // dummy
        print (args) // [1, 2, 3]
    }
    
  • Or, explicitly include type of args, e.g.

    [1, 2].satisfy { (args: Int...) in
        print (args) // [1, 2, 3]
    }
    

    Note that Generator.Element resolves to Int in this example above.


Swift 3.0-dev的当前状态

如上所述,令人好奇的是,这个bug

  • inout: is apparently no longer present in Swift 2.2 or Swift 3.0-dev for inout arguments, w.r.t. the issues described in Q&A 2. as linked to above

    • it was possibly fixed as bug [SR-7] was resolved (-> Swift 2.2)
    • however, seems to be regression 2.2->3.0-dev, w.r.t. type inference for inout arguments, as reported in bug report [SR-892]. E.g. the following snippet works in Swift 2.2, but not in 3.0-dev (minimally modified snipper from bug report [SR-7])

      func f(inout a: Int) {}
      let g = { x in f(&x) }  // OK 2.2, crashes 3.0-dev
      
  • variadic: is still present in Swift 2.2 as well as Swift 3.0-dev for variadic arguments (this thread and Q&A 1. above).

    • a more condensed example of the bug:

      let a: (Int...) -> () = { (args) in print(args) }         // bug: crashes
      let b: (Int...) -> () = { (args: Int...) in print(args) } // explicitly state argument type, OK
      let c: (Int...) -> () = { (args) in (); print(args) }     // extend to more than single line closure, OK
      

(对于Swift 3.0-dev,在使用IBM Swift Sandbox运行Swift 3.0-dev测试过。)


鉴于上述在Q&A 1. above中讨论的限制/错误,我认为已经有人为此问题提交了错误报告。然而,在Swift JIRA中我找不到任何相关信息,因此我现在已经在这里; SR-1197提交了一个错误报告。 - dfrib
另外,这让我很好奇实现会如何处理在闭包中使用 $0、$1 等变量,因为它们并不一定存在。 - JuJoDi
我想我必须收回之前的说法。使用解决方法和IBM Swift沙盒,我发现$0是args的引用,而不是args [0]的引用。 - JuJoDi
@JuJoDi 是的,只有一个可变参数的闭包将只允许一个单一的参数(例如 args$0),它将是一个数组。请注意,使用匿名参数,例如上面的 $0 而不是 args,将等同于 bug/崩溃情况,因为我们(就像崩溃情况一样)相信编译器能够正确地推断 $0 的类型。可能破损的类型推断是这个特定 bug/崩溃的根源。 - dfrib

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接