Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I cast an __NSMallocBlock__ to its underlying type in Swift 3?

I had a trick to help test UIAlertController that worked in Swift 2.x:

extension UIAlertController {

    typealias AlertHandler = @convention(block) (UIAlertAction) -> Void

    func tapButtonAtIndex(index: Int) {
        let block = actions[index].valueForKey("handler")
        let handler = unsafeBitCast(block, AlertHandler.self)

        handler(actions[index])
    }

}

This fails under Swift 3.x with fatal error: can't unsafeBitCast between types of different sizes, which tempts me to believe there might be a way to make the cast work. Can anyone figure it out?

like image 306
Robert Atkins Avatar asked Nov 16 '16 09:11

Robert Atkins


Video Answer


2 Answers

Found a solution that works in Swift 3.0.1

extension UIAlertController {

    typealias AlertHandler = @convention(block) (UIAlertAction) -> Void

    func tapButton(atIndex index: Int) {
        if let block = actions[index].value(forKey: "handler") {
            let blockPtr = UnsafeRawPointer(Unmanaged<AnyObject>.passUnretained(block as AnyObject).toOpaque())
            let handler = unsafeBitCast(blockPtr, to: AlertHandler.self)
            handler(actions[index])
        }
    }

}

(Originally, the block value was the actual block, not a pointer to the block—which you obviously can't cast to a pointer to AlertHandler)

like image 83
Robert Atkins Avatar answered Oct 09 '22 06:10

Robert Atkins


My answer is based on @Robert Atkins's, but shorter. The problem here is that, valueForKey returns a Any typed object, and because in Swift,

MemoryLayout<Any>.size == 32
MemoryLayout<AnyObjcBlockType>.size == 8

an assertion will be triggered in unsafeBitCast when casting between types of different sizes.

One work-around is to create an intermediate wrapper and transform back to raw pointer, which satisfies MemoryLayout<UnsafeRawPointer>.size == 8.

A much simpler way is to create an indirect reference directly using protocol AnyObject, relying on the fact that MemoryLayout<AnyObject >.size == 8, we can write following valid code:

typealias AlertHandler = @convention(block) (UIAlertAction) -> Void

func tapButton(atIndex index: Int) {
    if let block = actions[index].value(forKey: "handler") {
        let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
        handler(actions[index])
    }
}
like image 37
CrystDragon Avatar answered Oct 09 '22 04:10

CrystDragon