8

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

}

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

4

3 回答 3

22

找到适用于 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

于 2016-11-16T14:33:15.523 回答
10

我的回答基于@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])
    }
}
于 2018-05-22T07:06:21.523 回答
1

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

解雇(动画:真,完成:{()在处理程序(self.actions [index])})

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

于 2017-08-16T15:11:05.183 回答