Search code examples
iosuikitfirst-responderuiresponder

UIApplication's sendAction() works even if target(forAction:…) is nil


This code is called when a subview (imageSlideshow) is tapped:

        imageSlideshow.didTap = { [weak self] in
        guard UIApplication.shared.sendAction(#selector(ImageCarouselViewResponder.didTapImageCarousel(sender:)), to: nil, from: self, for: nil) else {
            assertionFailure("No target for ImageCarouselViewResponder.didTapImageCarousel(sender:)")
            return
        }
        …

This works as expected, as you can see from this debugging output:

(lldb) po UIApplication.shared.sendAction(#selector(ImageCarouselViewResponder.didTapImageCarousel(sender:)), to: nil, from: self, for: nil) 
true

However, when I try to ask for the target with the same selector, it's nil:

(lldb) po UIApplication.shared.target(forAction: #selector(ImageCarouselViewResponder.didTapImageCarousel(sender:)), withSender: self)
nil

Furthermore, I've added an extension to walk the responder tree to find the first responder, and it returns nil in this situation.

How is sendAction(…) working if there is no first responder?


Solution

  • Calling target(forAction:) will only pass the request up the responder chain from that responder, but the only thing after the application in the responder chain is the application delegate. Unless this method is implemented in either of those two, calling target(forAction:) on the application instance would return nil.

    Calling sendAction(_:to:from:for:) with a nil target works because it sends the message to the first responder which passes it up the responder chain until it is handled.