iOS Responder Chain или Что спрашивают на собеседовании

    image


    Какая разница между первым и вторым примером?

    За что отвечает таргет?

    В каком случае вызывается метод при нажатие кнопки?

    TL;DR


    При нажатии на кнопку наш метод вызывается в обоих случаях.


    Только в первом примере UIKit попытается вызвать метод в назначенном таргете(у нас это ViewController). Будет краш, если этого метода не существует.


    Во втором же примере используется iOS Responder Chain, UIKit будет искать самого ближнего UIResponder-a у которого есть данный метод. Краша не будет, если наш метод не найден.


    UIViewController, UIView, UIApplication наследуют от UIResponder.


    iOS Responder Chain и что под капотом


    Всем процессом iOS Responder Chain занимается UIKit, который динамично работает со связным списком UIResponder-ов. Этот список UIKit создает из first responder(первый UIResponder который зарегистрировал событие, у нас это UIButton(UIView) и его subviews.


    image


    UIKit проходит через список UIResponder-ов и проверяет с помощью canPerformAction на наличие нашей функции.


    open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool

    Если выбранный UIResponder не может работать с конкретным методом,
    UIKit рекурсивно посылает действия к следующему UIResponder-у в списке с помощью метода target который возвращает следующего UIResponder-а.


    open func target(forAction action: Selector, withSender sender: Any?) -> Any?

    Этот процесс повторяется до тех пор, пока кто-то из UIResponder-ов сможет работать с нашим методом или список закончится и это событие система проигнорирует.


    Во втором примере нажатия обработалось UIViewController-ом, но UIKit сначала отправил запрос к UIView так как он был first responder. У него не было нужного метода, поэтому UIKit перенаправил действия на следующего UIResponder-а в связном списке кем являлся UIViewController у которого был нужный метод.


    В большинстве случаев iOS Responder Chain это простой связной список subviews, но его очередность можно изменить. Можно заставить UIResponder (becomeFirstResponder) стать
    первым UIResponder и вернуть его к старой позиции с помощью resignFirstResponder. Это часто используется с UITextField для показа клавиатуры которая будет вызвана, только когда UITextField является first responder-ом.


    iOS Responder Chain и UIEvent


    The Responder Chain так же участвует при касаниях экрана, движениях, нажатиях. Когда система определяет какое-то события(touch, motion, remote-control, press), под капотом создается UIEvent и отправляется с помощью метода UIApplication.shared.sendEvent() к UIWindow. После получения события UIWindow определяет с помощью метода hitTest:withEvent к какому UIResponder данное событие принадлежит и назначает его first responder-ом. Дальше идет работа с связным списком UIResponder-ов описанная выше.


    Что бы работать с системными UIEvent-ами, сабклассы UIResponder (UIViewController, UIView, UIApplication) могут переопределить данные методы:


    open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
    open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
    open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
    open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
    open func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?)
    open func pressesChanged(_ presses: Set<UIPress>, with event: UIPressesEvent?)
    open func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?)
    open func pressesCancelled(_ presses: Set<UIPress>, with event: UIPressesEvent?)
    open func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
    open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
    open func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
    open func remoteControlReceived(with event: UIEvent?)

    Не смотря что возможность наследовать и вызывать sendEvent в ручную присутствует, UIResponder не предназначен для этого. Это может создать много проблем с работой кастомных событий, которые могут привести к не понятным действиям вызванными случайным first responeder-ом который может отреагировать на ваше событие.


    Чем это полезно, где использовать


    Не взирая на то, что iOS Responder Chain полностью контролируется UIKit-ом, его можно использовать для решения проблемы делегирования/общения. UIResponder действия похоже на одноразовые NotificationCenter.default.post.


    Возьмем пример, у нас есть рут UIViewController, который глубоко находится в стеке UINavigationController и нам нужно ему передать что произошло при нажатие кнопки на другом экране. Можно воспользоваться делагат паттерном или NotificationCenter.default.post, но довольно простой вариант это использования iOS Responder Chain.


    button.addTarget(nil, action: #selector(RootVC.doSomething), for: .touchUpInside)

    При нажатие будет вызываться метод в рут UIViewController. #selector может принимать следующие параметры:


    @objc func doSomething()
    @objc func doSomething(sender: Any?)
    @objc func doSomething(sender: Any?, event: UIEvent?)

    sender это объект который отправил событие — UIButton, UITextField и так далее.


    Дополнительные ресурсы для изучения [eng]:


    Хорошое описание UIEvent, UIResponder и пару продвинутых примеров(координатор патерн)
    Подробная статья о ios responder chain
    Пример responder chain на практике
    Офф дока по iOS responder chain
    Офф дока по UIResponder

    • +14
    • 4,8k
    • 3
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 3

      0
      button.addTarget(nil, action: #selector(RootVC.doSomething), for: .touchUpInside)

      RootVC.doSomething — это метод класса, а не инстанса?

        0
        Это инстанс метод, так как мы вызываем его с помощью #selector(), и он будет определен во время runtime.
        0
        Хорошее понятное разъяснение, спасибо!

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое