Хабрахабр

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

image

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

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

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

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

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

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

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

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

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-ов сможет работать с нашим методом или массив закончится и это событие система проигнорирует.

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

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

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

Что бы работать с системными 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?)

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

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

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

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

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

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

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

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть