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

Rob*_*ins 6 casting unsafe-pointers swift swift3

我有一个技巧来帮助测试UIAlertController在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])
    }

}
Run Code Online (Sandbox Code Playgroud)

这在Swift 3.x下失败了fatal error: can't unsafeBitCast between types of different sizes,这诱使我相信可能有一种方法可以让演员工作.任何人都可以搞清楚吗?

Rob*_*ins 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])
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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


Cry*_*gon 9

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

MemoryLayout<Any>.size == 32
MemoryLayout<AnyObjcBlockType>.size == 8
Run Code Online (Sandbox Code Playgroud)

在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])
    }
}
Run Code Online (Sandbox Code Playgroud)