Какая диспетчеризация используется, если класс наследуется от NSObject, но не помечен как dynamic
Что проверяют
Понимаете ли вы, что наследование от NSObject само по себе не переводит класс на отправку сообщений Objective-C. Интервьюер ждёт разбора по месту объявления метода, а следом почти всегда спрашивает, почему без dynamic не работают KVO и swizzling.
Как отвечать
В Swift три вида диспетчеризации: прямая (статическая), табличная (через vtable) и отправка сообщений через objc_msgSend. Для потомка NSObject без dynamic картина такая:
- метод в теле класса: табличная диспетчеризация. Даже с
@objcвызов из Swift идёт через vtable, атрибут лишь регистрирует селектор в рантайме Objective-C, чтобы метод был виден из ObjC и через#selector; - метод в extension без
@objc: прямая диспетчеризация, переопределить его в наследнике компилятор не даст; - метод в extension с
@objc: отправка сообщения, поэтому такой метод можно переопределять; finalи, как правило,private: прямая диспетчеризация, компилятор может заинлайнить вызов;- методы, объявленные в самом Objective-C (например, унаследованные от
UIView): всегда черезobjc_msgSend.
dynamic меняет правило для вызовов из Swift: они тоже идут через рантайм Objective-C, и тогда работают KVO, method_exchangeImplementations и прочая подмена реализаций. Начиная со Swift 4 @objc больше не выводится автоматически для всех членов потомка NSObject, а начиная со Swift 5 dynamic не подразумевает @objc, поэтому для KVO пишут @objc dynamic var.
Хорошо закончить выводом: наследование от NSObject даёт совместимость с Objective-C, но не меняет способ вызова из Swift-кода.
Где спотыкаются
Отвечают «раз NSObject, значит всё через objc_msgSend». Так бывает только при пометке dynamic или для методов, пришедших из Objective-C.
Путают @objc и dynamic. Первый делает метод видимым для рантайма, второй заставляет Swift ходить через этот рантайм. Отсюда swizzling, который «иногда не срабатывает»: вызов из Swift прошёл по vtable мимо подменённой реализации.
Забывают про extension. Попытка переопределить в наследнике метод из расширения без @objc заканчивается ошибкой компиляции, и кандидат не может объяснить почему.