Cast closures/blocks



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

例如,我通过-[CAAnimation setValue:forKey:]将它们分配给CAAnimation,这样我就可以在动画完成时执行块。(Objective-C可以把block当作对象;你也可以做[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'
}

我已经找到了一个解决方案,但它很丑,IMHO:在我的桥接头,我有:

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的方式解决这个问题呢?

如何使用函数类型参数化通用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

我喜欢GoZoner的解决方案-将块包装在自定义类中-但是既然您要求实际的"Swift方式"来执行块和AnyObject之间的强制转换,我就给出这个问题的答案:使用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)

这是另一个解决方案,允许我们与Objective-C交换值。它建立在GoZoner将函数包装在类中的想法之上;不同之处在于我们的类是一个NSObject子类,因此可以在没有任何hack的情况下为Objective-C提供块内存管理功能,并且可以直接用作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")

您所需要做的就是使用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>  

最新更新