Можно ли вызвать функцию у объекта класса в Swift, если она не реализована в коде
Что проверяют
Различаете ли вы статическую проверку Swift и динамику Objective-C runtime. Следом обычно спрашивают про опциональные методы делегатов и про падение unrecognized selector sent to instance.
Как отвечать
В чистом Swift нет: вызов несуществующего метода не скомпилируется с ошибкой value of type 'X' has no member 'foo'. Но есть легальные механизмы, при которых класс сам метод не пишет, а вызов работает:
- реализация по умолчанию в расширении протокола: протокол объявляет требование,
extensionдаёт реализацию, и класс получает её бесплатно. Тонкость: если метод есть только в расширении, а в протоколе не объявлен, диспетчеризация статическая, и версия класса через переменную протокольного типа не вызовется; - опциональные требования
@objcпротокола:@objc optional func didFinish(), вызов через опциональную цепочкуdelegate?.didFinish?(). Если класс метод не реализовал, вызов просто ничего не сделает. Работает только для классов и протоколов с@objc; - Objective-C runtime: у наследников
NSObjectселектор вызывается черезperform(_:). Перед этим проверяютresponds(to:), а сам метод можно добавить на лету вresolveInstanceMethod(_:)черезclass_addMethodили перенаправить объекту изforwardingTarget(for:); AnyObject: у значения этого типа компилятор разрешает вызвать любой видимый@objcметод, а вызов идёт через опциональную цепочку;@dynamicMemberLookupи@dynamicCallable: обращение к членам, которых в типе нет, компилятор разворачивает в ваш сабскрипт или метод. На этом строят обёртки над JSON.
Где спотыкаются
Вызывают perform без responds(to:). Если селектора нет, приложение падает с NSInvalidArgumentException и сообщением unrecognized selector sent to instance.
Пытаются сделать optional требование в обычном Swift-протоколе. Без @objc такого модификатора нет, в чистом Swift опциональность эмулируют реализацией по умолчанию в расширении.
Ожидают полиморфизма от метода, который объявлен только в расширении протокола. Класс его «переопределил», но через переменную протокольного типа вызывается версия из расширения, и баг выглядит мистически.