在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方式解决这个问题?
答案 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>