15

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?

DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • 1
    The line `animation.setValue(completion, forKey: "completionClosure")` does not compile in my Xcode 6 beta 2 project. – Martin R Jul 05 '14 at 13:54
  • 1
    @MartinR: Thanks, was still using beta 1. Have updated the question… it got worse. – DarkDust Jul 05 '14 at 15:43

4 Answers4

11

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
GoZoner
  • 67,920
  • 20
  • 95
  • 145
  • 1
    Sorry for the late answer. This actually works! `let completion = anim.valueForKey("completionClosure") as? Block<()->Void> ; completion?.f()` No way to bridge to Objective-C, though, but so far this the best solution. – DarkDust Jul 17 '14 at 16:48
  • 1
    This is a great solution. Thank u very much – fnc12 Dec 23 '14 at 15:07
9

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)
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Does this still work? I got the error "fatal error: can't unsafeBitCast between types of different sizes". For `ch` var i had this: ch = {()->String in return "works"}. Did this work for you? – Just a coder Oct 15 '15 at 01:42
  • @Jai I don't know; I haven't used `unsafeBitCast` for this in a long time. I've adopted GoZoner's solution of a generic wrapper. Note that you _still_ might need to typealias your completion handler type using `@convention(block)` in order to get Objective-C memory management on it. – matt Oct 15 '15 at 01:53
  • ok it works now. In your code you had `let ch = // a completion handler closure` And i didnt know what to put there. So i Had this -> ` let ch = {()->String in return "yes it works"}` Now this fails with error "fatal error: can't unsafeBitCast between types of different sizes". It only worked when i put this `let ch: MyTypeDefToReturnAString = {()->String in return "yes"}`. It seems to only work when you explicitly put in your custom typedefalias. Wish you had that in your code above to save me the headache. Thanks anyways. + 1 – Just a coder Oct 15 '15 at 02:09
  • I _do_ have a custom typealias in my code. It's the first line of my code. See it? – matt Oct 15 '15 at 02:10
  • yea i saw it there, but not here `let ch = // a completion handler closure` ..forgive my noobism but i didnt know what code to write after the `// a completion handler closure` – Just a coder Oct 15 '15 at 02:14
4

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")
matt
  • 515,959
  • 87
  • 875
  • 1,141
2

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>  
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143