如何在Swift 3中将__NSMallocBlock__转换为其基础类型?

时间:2016-11-16 09:50:37

标签: swift casting swift3 unsafe-pointers

我有a trick来帮助测试在Swift 2.x中运行的UIAlertController

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])
    }

}

这在Swift 3.x下使用fatal error: can't unsafeBitCast between types of different sizes失败,这诱使我相信可能有一种方法可以使演员工作。有人能想出来吗?

3 个答案:

答案 0 :(得分:19)

找到一个适用于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])
        }
    }

}

(最初,block值是实际的块,而不是指向块的指针 - 您显然无法将其转换为指向AlertHandler的指针

答案 1 :(得分:3)

我的答案基于@Robert Atkins,但更短。 这里的问题是,valueForKey返回一个Any类型的对象,因为在Swift中,

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

当在不同大小的类型之间进行转换时,将在unsafeBitCast中触发断言。

一种解决方法是创建一个中间包装器并转换回原始指针,满足MemoryLayout<UnsafeRawPointer>.size == 8

更简单的方法是使用协议AnyObject直接创建间接引用,依赖MemoryLayout<AnyObject >.size == 8这一事实,我们可以编写以下有效代码:

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])
    }
}

答案 2 :(得分:1)

如果您的UIAlertController是一个操作表,您可以修改Robert的答案,在执行处理程序之前解除UIAlertController。

dismiss(动画:true,完成:处理程序中的{()(self.actions [index])})

我正在使用这个扩展进行测试,如果没有这个修改,我提出的视图控制器的断言就失败了。