【问题标题】:Cast closures/blocks铸造闭包/块
【发布时间】:2014-07-05 12:11:35
【问题描述】:

在 Objective-C 中,我经常绕过块。我经常使用它们来实现有助于避免将内容存储到实例变量中的模式,从而避免线程/时序问题。

例如,我通过-[CAAnimation setValue:forKey:] 将它们分配给CAAnimation,这样我就可以在动画完成时执行该块。 (Objective-C 可以将块视为对象;您也可以这样做 [someBlock copy][someBlock release]。)

然而,在 Swift 中尝试将这些模式与 Objective-C 一起使用似乎非常困难。 (编辑:我们可以看到该语言仍在不断变化:已经修改了代码,使其适用于 Xcode6-beta2,之前的版本适用于 Xcode6-beta1。)

例如,我无法将 AnyObject 转换回块/闭包。以下会从编译器产生错误:

override func animationDidStop(anim: CAAnimation!, finished flag: Bool)
{
    let completion : AnyObject! = anim.valueForKey("completionClosure")
    (completion as (@objc_block ()->Void))()
    // Cannot convert the expression's type 'Void' to type '@objc_block () -> Void'
}

我找到了一种解决方法,但它非常难看,恕我直言:在我的桥接头中,我有:

static inline id blockToObject(void(^block)())
{
    return block;
}

static inline void callBlockAsObject(id block)
{
    ((void(^)())block)();
}

现在我可以在 Swift 中做到这一点:

func someFunc(completion: (@objc_block ()->Void))
{
    let animation = CAKeyframeAnimation(keyPath: "position")
    animation.delegate = self
    animation.setValue(blockToObject(completion), forKey: "completionClosure")
    …
}

override func animationDidStop(anim: CAAnimation!, finished flag: Bool)
{
    let completion : AnyObject! = anim.valueForKey("completionClosure")
    callBlockAsObject(completion)
}

它可以工作,但我需要为我想使用的每种块类型提供一个新函数,而且我正在修改编译器,这也不好。

那么有没有办法以纯 Swift 的方式解决这个问题?

【问题讨论】:

  • animation.setValue(completion, forKey: "completionClosure") 行在我的 Xcode 6 beta 2 项目中无法编译。
  • @MartinR:谢谢,还在使用 beta 1。已经更新了问题……它变得更糟了。

标签: objective-c closures swift block


【解决方案1】:

用函数类型参数化的泛型Block 怎么样?

class Block<T> {
  let f : T
  init (_ f: T) { self.f = f }
}

分配其中之一;它将是AnyObject 的子类型,因此可以分配到字典和数组中。这似乎并不太繁重,尤其是使用 尾随闭包 语法。使用中:

  5> var b1 = Block<() -> ()> { print ("Blocked b1") }
b1: Block<() -> ()> = {
  f = ...
}
  6> b1.f()
Blocked b1

另一个推断Block类型的例子:

 11> var ar = [Block { (x:Int) in print ("Block: \(x)") }]
ar: [Block<(Int) -> ()>] = 1 value {
  [0] = {
    f = ...
  }
}
 12> ar[0].f(111)
Block: 111

【讨论】:

  • 抱歉,回答迟了。这确实有效! let completion = anim.valueForKey("completionClosure") as? Block&lt;()-&gt;Void&gt; ; completion?.f() 虽然没有办法连接到 Objective-C,但到目前为止这是最好的解决方案。
  • 这是一个很好的解决方案。非常感谢你
【解决方案2】:

我喜欢 GoZoner 的解决方案 - 将块包装在自定义类中 - 但是由于您要求使用实际的“Swift 方式”在块和 AnyObject 之间执行转换,所以我将给出这个问题的答案:cast与unsafeBitCast。 (我猜这和 Bryan Chen 的reinterpretCast 差不多,已经不存在了。)

所以,在我自己的代码中:

typealias MyDownloaderCompletionHandler = @objc_block (NSURL!) -> ()

注意:在 Swift 2 中,这将是:

typealias MyDownloaderCompletionHandler = @convention(block) (NSURL!) -> ()

这是一个方向的演员:

// ... cast from block to AnyObject
let ch : MyDownloaderCompletionHandler = // a completion handler closure
let ch2 : AnyObject = unsafeBitCast(ch, AnyObject.self)

这是另一个方向的演员:

// ... cast from AnyObject to block
let ch = // the AnyObject
let ch2 = unsafeBitCast(ch, MyDownloaderCompletionHandler.self)
// and now we can call it
ch2(url)

【讨论】:

  • 这还能用吗?我收到错误“致命错误:不能在不同大小的类型之间使用 unsafeBitCast”。对于ch var,我有这个:ch = {()->String in return "works"}。这对你有用吗?
  • @Jai 我不知道;我很长时间没有为此使用unsafeBitCast。我采用了 GoZoner 的通用包装器解决方案。请注意,您仍然可能需要使用 @convention(block) 为您的完成处理程序类型键入别名,以便对其进行 Objective-C 内存管理。
  • 好的,现在可以了。在您的代码中,您有 let ch = // a completion handler closure 而我不知道该放什么。所以我有这个 -> ` let ch = {()->String in return "yes it works"}` 现在失败并出现错误 "fatal error: can't unsafeBitCast between types of different size"。仅当我输入此let ch: MyTypeDefToReturnAString = {()-&gt;String in return "yes"} 时才有效。它似乎仅在您明确输入自定义 typedefalias 时才有效。希望你在上面的代码中有这个,以免让我头疼。无论如何谢谢。 + 1
  • 确实在我的代码中有一个自定义类型别名。这是我的代码的第一行。看到了吗?
  • 是的,我在那里看到了,但这里没有 let ch = // a completion handler closure ..原谅我的菜鸟,但我不知道在 // a completion handler closure 之后要写什么代码
【解决方案3】:

这是另一个解决方案,允许我们使用 Objective-C 进行转换以交换值。它建立在 GoZoner 将函数包装在一个类中的想法之上;不同的是我们的类是NSObject的子类,因此可以给Objective-C块内存管理功能,无需任何hacky,可以直接作为AnyObject交给Objective-C:

typealias MyStringExpecter = (String) -> ()
class StringExpecterHolder : NSObject {
    var f : MyStringExpecter! = nil
}

以下是如何使用它来包装函数并传递 AnyObject 预期的位置:

func f (s:String) {println(s)}
let holder = StringExpecterHolder()
holder.f = f

let lay = CALayer()
lay.setValue(holder, forKey:"myFunction")

下面是稍后提取函数并调用它的方法:

let holder2 = lay.valueForKey("myFunction") as StringExpecterHolder
holder2.f("testing")

【讨论】:

    【解决方案4】:

    您需要做的就是使用reinterpretCast 执行强制转换。

    (reinterpretCast(completion) as (@objc_block Void -> Void))()
    

    来自 REPL

      1> import Foundation
      2> var block : @objc_block Void -> Void = { println("test")}
    block: @objc_block Void -> Void =
      3> var obj = reinterpretCast(block) as AnyObject // this is how to cast block to AnyObject given it have @objc_block attribute
    obj: __NSMallocBlock__ = {}
      4> var block2 = reinterpretCast(obj) as (@objc_block Void -> Void)
    block2: (@objc_block Void -> Void) =
      5> block2()
    test
      6>  
    

    【讨论】:

    • '@objc_block' 属性已弃用;应该使用“@convention(block)”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多