Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast closures/blocks

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?

like image 483
DarkDust Avatar asked Jul 05 '14 12:07

DarkDust


People also ask

How many types of closures are there?

Some common types of closures include continuous thread closures (CT), disc top caps, child resistant (CRC) closures, pumps, and sprayers. A CT cap is your basic closure that can be easily sealed and resealed. Disc top caps allow the user to dispense product without having to remove the cap.

What are blocks in Swift?

Blocks are first-class functions, which is a fancy way of saying that Blocks are regular Objective-C objects. Since they're objects, they can be passed as parameters, returned from methods and functions, and assigned to variables.

How do closures work in Swift?

Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages. Closures can capture and store references to any constants and variables from the context in which they're defined. This is known as closing over those constants and variables.

How do I call closure in Swift?

If I want to run my closure, I call it like a function myClosure() . This will cause the code to print the current value of counter . By writing [counter] in we create a capture list that takes a snapshot of the current value of counter which will cause us to ignore any changes that are made to counter .


2 Answers

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
like image 67
GoZoner Avatar answered Sep 27 '22 18:09

GoZoner


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)
like image 21
matt Avatar answered Sep 27 '22 18:09

matt