IBOutlet 崩溃 EXC_BAD_ACCESS 即使不是零

IBOutlet crashing with EXC_BAD_ACCESS even though not nil

在一个 UIViewController (rolePageController) 中,我配置了另一个 UIViewController (drawerController) 并从角色页面传递给它 2 个 UIView,这将成为 drawerController 配置的一部分。一旦 drawerController 尝试从 rolePageController 访问 IBOutlet 视图,它就会崩溃并显示 EXC_BAD_ACCESS(代码=EXC_I386_GPFLT)。

在第一个 VC (rolePageController) 中,这里是 IBOutlets:

@IBOutlet var rolePageDrawerView: UIView!
@IBOutlet var rolePageContentView: UIView!

在 rolePageController.viewDidLoad() 中我调用了 drawerController.configureDrawer(...):

override func viewDidLoad() {
    super.viewDidLoad()

    //other stuff happens here

    let drawerController = UIStoryboard(name: "StoryboardName", bundle: nil).instantiateViewController(withIdentifier: "drawerController") as! DrawerViewController
    drawerController.configureDrawer(drawerContainerView: self.rolePageDrawerView, overlaidView: self.rolePageContentView)

    //other stuff here
}

DrawerViewController 协议定义为:

protocol DrawerViewController where Self: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}

这是 configureDrawer(...) 函数的代码:

private var drawerParentView: UIView!
private var overlaidByDrawerView: UIView!


func configureDrawer(drawerContainerView: UIView, overlaidView: UIView) {
    self.drawerParentView = drawerContainerView
    self.overlaidByDrawerView = overlaidView
}

在调试器中注意到,调用的 drawerController 实例与接收调用的 self 实例不匹配。这是将要调用的实例的地址:

这是我进入调用时实例的地址:

调用前drawerController的地址不是我step调用时self的地址。那永远不应该发生。

我创建了一个简化的项目来重现 https://github.com/ksoftllc/DynamicStackBufferOverflow 的崩溃。

解决方案 解决方案原来是从 DrawerViewController 协议中删除 where 子句。

protocol DrawerViewController where Self: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}

将此函数调用从 viewDidLoad 移至 viewWillAppear drawerController.configureDrawer(drawerContainerView: self.rolePageDrawerView, overlaidView: self.rolePageContentView)

dynamic-stack-buffer-overflow 与递归没有任何关系。这意味着 alloca 缓冲区已溢出。检查 asan runtime source code.

假设堆栈的布局使得您有一个 alloca 缓冲区,后跟一个对象指针——甚至可能是作为参数传递的对象指针之一。

假设 alloca 缓冲区溢出。在 asan 构建中,这会触发 dynamic-stack-buffer-overflow 错误。但在非 asan 构建中,它只是覆盖该对象指针的字节。假设它写入的字节构成了一个地址,该地址未映射到您的进程页面 table.

如果程序试图读取该对象指针并将其存储在别处(例如,在一个实例变量中),它必须增加该对象的引用计数。但这意味着取消对指针的引用——并且指针指向一个未映射的地址。也许这会导致一般性保护错误,Mach calls an EXC_I386_GPFLT

如果您发布了 asan dynamic-stack-buffer-overflow 错误的堆栈跟踪以及导致错误的代码的反汇编,将会很有帮助。

找到了有问题的代码,但我不知道为什么这会导致我看到的错误。 drawerController 符合 DrawerViewController 协议,定义为:

protocol DrawerViewController where Self: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}

当我删除 Where 条件时,它不再崩溃。

protocol DrawerViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}

where 子句对于程序的正确运行实际上不是必需的,因此我将在没有它的情况下继续。

更新 我用 swift.org 提交了一个错误并收到了回复。 Swift 4.2 不支持向协议添加 where 子句,但 Swift 5.0 将支持。此外,@J Doe 在下面发布了一种通过更新 Xcode 工具包来实现此目的的方法。

它真的看起来像 Swift 编译器错误。我简化了您的代码以进行说明:

func foo(_ wow: TestProtocol) {
    wow.foo()
}

protocol TestProtocol where Self: NSObject {
    func foo()
}

class TestClass: NSObject, TestProtocol {

    func foo() {
        print("Much wow")
    }

}

foo(TestClass())

您可以将此报告为错误。要解决此问题,我建议您不要使用 where 语句或传递类型为 func foo(_ wow: TestClass {.

的对象

要解决您的问题,运行 它在开发 t运行k 工具链快照上。您可以在这里下载:

https://swift.org/download/

转到快照 -> T运行k 开发(主)XCode(所以不是 Swift 5.0)并下载快照截至 12 月 15 日(我从 11 月 30 日拿到了那个,但我相信 12 月 15 日也可以。)

安装工具链后,在 XCode 中转到:File -> Preferences -> Components 和 select 最新的工具链。 现在 运行 没有任何崩溃

此外,其中 Self: UIViewController 可以缩短为 :UIViewcontroller这仅适用于最新的工具链):

protocol DrawerViewController: UIViewController {
    func configureDrawer(drawerContainerView: UIView, overlaidView: UIView)
}