Skip to content

NSInvalidArgumentException unrecognized selector crash due to OverlayScrollViewDelegateProxy deallocation issue #100

@bshuttkg

Description

@bshuttkg

Describe the bug

Memory deallocation issue in the OverlayScrollViewDelegateProxy.

Environnement

  • Device: iPhone 16 Pro Simulator
  • OS: iOS 18.2
  • OverlayContainer Version: aa7bd20

Description

As you know, the proxy sets itself as the UIScrollViewDelegate:

scrollView.delegate = self

For our use case, the scrollView is a UITableView; so the delegate is a UITableViewDelegate.
As a result, it will be sent messages like tableView:viewForHeaderInSection:.

The framework does consider this, as we see in the following code to forward on other messages:

override func responds(to aSelector: Selector!) -> Bool {
    let originalDelegateRespondsToSelector = originalDelegate?.responds(to: aSelector) ?? false
    return super.responds(to: aSelector) || originalDelegateRespondsToSelector
}

override func forwardingTarget(for aSelector: Selector!) -> Any? {
    if originalDelegate?.responds(to: aSelector) == true {
        return originalDelegate
    } else {
        return super.forwardingTarget(for: aSelector)
    }
}

where:

private weak var originalDelegate: UIScrollViewDelegate?

But there is a bug here, a few:

  1. In ARC in Swift: Basics and Beyond, we see how Swift is non-lexical. Above, in forwardingTarget(for:), originalDelegate may respond to a selector and be deallocated when it is returned (because it is weak)
  2. originalDelegate may cause responds(to:) to return true, but have been deallocated in forwardingTarget(for:)

The second is why we are seeing crashes in our app.
Just before the crash, forwardingTarget(for:) is called with selector tableView:viewForHeaderInSection: and the originalDelegate is nil.

Returning nil for this scenario in forwardingTarget(for:) does not stop the invocation.

So we are experiencing the following crash in our app:

'NSInvalidArgumentException', reason: '-[OverlayContainer.OverlayScrollViewDelegateProxy tableView:viewForHeaderInSection:]: unrecognized selector sent to instance'

It looks to me like you might have done the same as this SO answer.
Currently, I don't think it's a correct implementation of a proxy in Swift for weak references.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions