在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>