@dynamicReplacement causes infinite recursion?

Hello everyone!

For testing purposes, I am trying to replace the implementation of UIViewController's present method using the @_dynamicReplacement annotation.

The first thing this replacement does is call the original, then it does its own work.

extension UIViewController {
    @_dynamicReplacement(for: present(_:animated:completion:))
    func testablePresent(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        self.present(viewControllerToPresent, animated: flag, completion: completion)
        // testing stuff...

I thought that when the replacement implementation calls the method being replaced, it's supposed to call the original implementation. Not itself. That's what the original pitch says should happen anyway.

But with the code above, I'm definitely seeing infinite recursion. The call stack starts off like this:

in @nonobjc UIViewController.present(_:animated:completion:) ()
in UIViewController.mockedPresent(_:animated:completion:) at MyTests.swift:190
in @objc UIViewController.mockedPresent(_:animated:completion:) ()
in <source code method that calls present()>

It seems to looping through a few intermediate methods. For whatever reason @nonobjc UIViewController.present calls back into @objc UIViewController.mockedPresent.

Maybe it has something to do with present being an objc method? I'm not really sure what the problem is.

So far I've tried:

  • using some compiler flags that I read about but which didn't seem to actually be supported anymore. -Xfrontend -enable-dynamic-replacement-chaining
  • adding @nonobjc to the declarations.

Any help or advice would be appreciated! Thanks!

Terms of Service

Privacy Policy

Cookie Policy