In Objective-C, I often pass around blocks. I use them very often to implement patterns that help avoid storing stuff into instance variables, thus avoiding threading/timing issues.
For example, I assign them to a CAAnimation
via -[CAAnimation setValue:forKey:]
so I can execute the block when the animation is finished. (Objective-C can treat blocks as objects; you also can do [someBlock copy]
and [someBlock release]
.)
However, trying to use these patterns in Swift together with Objective-C seems to be very difficult. (Edit: and we can see that the language is still in flux: have adapted the code so it works on Xcode6-beta2, previous version worked on Xcode6-beta1.)
For example, I can't convert AnyObject
back to a block/closure. The following yields an error from the compiler:
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'
}
I have found a workaround, but it's pretty ugly, IMHO: in my bridging header, I have:
static inline id blockToObject(void(^block)())
{
return block;
}
static inline void callBlockAsObject(id block)
{
((void(^)())block)();
}
And now I can do this in 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)
}
It works, but I'd need a new function for every block type that I'd like to use and I'm hacking around the compiler which can't be good either.
So is there a way to solve this in a pure Swift way?
How about a generic Block
parameterized with the function type?
class Block<T> {
let f : T
init (_ f: T) { self.f = f }
}
Allocate one of these; it will be a subtype of AnyObject
and thus be assignable into dictionaries and arrays. This doesn't seem too onerous especially with the trailing closure syntax. In use:
5> var b1 = Block<() -> ()> { print ("Blocked b1") }
b1: Block<() -> ()> = {
f = ...
}
6> b1.f()
Blocked b1
and another example where the Block
type is inferred:
11> var ar = [Block { (x:Int) in print ("Block: \(x)") }]
ar: [Block<(Int) -> ()>] = 1 value {
[0] = {
f = ...
}
}
12> ar[0].f(111)
Block: 111
I like GoZoner's solution - wrap the block in a custom class - but since you asked for the actual "Swift way" to perform the cast between a block and an AnyObject, I'll just give the answer to that question: cast with unsafeBitCast
. (I'm guessing that this is more or less the same as Bryan Chen's reinterpretCast
, which no longer exists.)
So, in my own code:
typealias MyDownloaderCompletionHandler = @objc_block (NSURL!) -> ()
Note: in Swift 2, this would be:
typealias MyDownloaderCompletionHandler = @convention(block) (NSURL!) -> ()
Here's the cast in one direction:
// ... cast from block to AnyObject
let ch : MyDownloaderCompletionHandler = // a completion handler closure
let ch2 : AnyObject = unsafeBitCast(ch, AnyObject.self)
Here's the cast back in the other direction:
// ... cast from AnyObject to block
let ch = // the AnyObject
let ch2 = unsafeBitCast(ch, MyDownloaderCompletionHandler.self)
// and now we can call it
ch2(url)
Here's yet another solution, allowing us to cast to exchange values with Objective-C. It builds on GoZoner's idea of wrapping the function in a class; the difference is our class is an NSObject subclass, and can thus give the function Objective-C block memory management without any hackery, and can be directly used as an AnyObject and handed over to Objective-C:
typealias MyStringExpecter = (String) -> ()
class StringExpecterHolder : NSObject {
var f : MyStringExpecter! = nil
}
Here's how to use it to wrap a function and pass where an AnyObject is expected:
func f (s:String) {println(s)}
let holder = StringExpecterHolder()
holder.f = f
let lay = CALayer()
lay.setValue(holder, forKey:"myFunction")
And here's how to extract the function later and call it:
let holder2 = lay.valueForKey("myFunction") as StringExpecterHolder
holder2.f("testing")
All you need to do is use reinterpretCast
to perform force cast.
(reinterpretCast(completion) as (@objc_block Void -> Void))()
from 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>