铸造闭合/块

时间:2014-07-05 12:11:35

标签: objective-c closures swift block

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

例如,我通过CAAnimation将它们分配给-[CAAnimation setValue:forKey:],这样我就可以在动画结束时执行该块。 (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方式解决这个问题?

4 个答案:

答案 0 :(得分:10)

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

答案 1 :(得分:9)

我喜欢GoZoner的解决方案 - 将块包装在自定义类中 - 但是因为你要求实际的&#34; Swift方式&#34;为了在一个块和一个AnyObject之间执行转换,我只是给出了这个问题的答案:使用unsafeBitCast进行转换。 (我猜这与布莱恩·陈的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)

答案 2 :(得分:4)

这是另一个解决方案,允许我们使用Objective-C进行交换。它建立在GoZoner将功能包装在一个类中的想法之上;不同的是我们的类是一个NSObject的子类,因此可以给出Objective-C块的内存管理功能而不需要任何hackery,并且可以直接用作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")

答案 3 :(得分:2)

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