Objective-C Runtime + Swift = ❤

Alexander Goremykin
Яндекс.Карты Mobile
3 min readMar 24, 2018

Трудно отрицать мощь Objective-C Runtime. В умелых руках с его помощью можно творить... трудно понимаемый код.

Шутка.

Так или иначе бывают моменты, когда использование Runtime’а очень кстати. И все становится немного интереснее, если вы пишите на Swift.

Предлагаю разобрать несколько полезных сниппетов кода.

Протаскивание приватного Objective-C метода в Swift

Начнем с чего попроще. Предположим у вас есть некоторый класс, написанный на Objective-C, у которого в @implementation секции реализуется некоторый метод:

-(void)veryUsefulMethod {}

При этом он не объявлен в интерфейсе класса.

Будь мы в мире победившего Objective-C, то все что нам требовалось бы сделать это реализовать метод с таким же названием в наследнике. Тривиально. Но что делать в Swift?

override func veryUsefulMethod()

Хорошая попытка, но нет. Безусловно такой код не соберется, ведь в текущем скоупе нету никакого veryUsefulMethod и, соответсвенно, оверайдить нечего.

На самом деле решение аналогично тому, что мы сделали бы в Objective-C — реализуем метод с аналогичным именем. Остается рассказать о нем Runtime’у и сделать это очень просто: необходимо добавить спецификатор @objc перед нашим свифтовым кодом.

@objc func veryUsefulMethod()

Если вам интересно глубже разобраться с диспатчем вызовов в Swift и почему @objc сработал, то советую обратиться к статье Браина Кинга абсолютно раскрывающей данную тему.

Ок, мы переопределили приватный метод, но как вызвать super реализацию?

Вызов приватных Objective-C методов из Swift

Зная подход к Runtime’у можно вытащить из него много полезной информации. Что у нас есть на руках? — Прототип приватного метода. Кажется, этого более чем достаточно, чтобы вызвать его! Действительно, зная прототип метода, мы можем создать его селектор и с его помощью получить посредствам функции class_getMethodImplementation IMP метода. IMP — тривиальный указатель на C — функцию. Тривиальный, но из Swift’а мы не сможем просто взять и вызвать его.

Но и тут от нас требуется не много. В Swift 2 было добавлено ключевое слово @convetion благодаря которому мы можем объявить тип замыкания, к которому в дальнейшем мы можем скастить указатель на C — функцию и без проблем вызвать его. Итак @convention + unsafeBitCast — то что нам нужно.

Полноценный оверайд приватного Objective-C метода в Swift

Давайте соберем все вместе и полноценно оверайднем метод veryUsefulMethod в Swift.

Казалось бы, за чем вообще писать об этом? Ведь очевидно, что надо просто собрать все то, что было сказано выше вместе. На самом деле, тут кроется одна особенность, на которой я и хочу заострить внимание.

Очень важно в вызов метода class_getMethodImplementation передавать тип своего родителя, а не свой тип. Иначе class_getMethodImplementation будет возвращать IMP текущего, а не super метода и мы получим бесконечный цикл, который довольно быстро закончится переполнением стека.

Method swizzling в Swift

Method swizzling позволяет подменить реализацию метода в Runtime. Известная техника, раскрывающая потенциал Objective-C Runtime.

По большому счету, в Swift method swizzling осуществляется аналогично тому, как это делается в Objective-C. Все что требуется это получить IMP целевого метода и метода который займет его место. Все это мы уже делали в предыдущих примерах. Имея на руках требуемые IMP, с помощью метода method_exchangeImplementations мы меняем их местами. Вот и все. Теперь, при попытке вызвать метод A, Runtime будет диспатчить вызов к нашему подставному методу B.

Однако, с переходом на Swift 4 оверайд class func initialize() был запрещен.

Значит, нам надо найти другую потокобезопасную альтернативу, гарантирующую выполнение блока кода один раз за все время работы программы. И такая альтернатива есть — static переменная.

Безусловно, у Objective-C Runtime еще огромное количество всякого интересного. Но, тем не менее, здесь мы рассмотрели самые ходовые техники, которые могут вам пригодиться.

--

--