Что такое objective c runtime

Обновлено: 06.07.2024

Это перевод. Как ученый со слабым английским языком, должно быть что-то не то, что он задумал. Перевод этой статьи является исключительно личным предпочтением. Я надеюсь получить советы и отзывы каждого. заинтересованы, вы можетеПосмотреть исходный текст。

Ниже приводится текст:

Вообще говоря, когда люди плохо знакомы с Cocoa / Objective-C, механизм времени выполнения (Objective-C Runtime) - одна из наиболее упускаемых из виду функций. Причина в том, что Objective-C - это простой язык, для начала работы с которым требуется несколько часов.После этого новички обычно тратят большую часть своего времени и энергии на изучение Cocoa Framework и способы ее использования. Однако каждый должен хотя бы знать, как работает среда выполнения, а не просто оставаться на когнитивном уровне метода компиляции, например: [target doMethodWith: var]; После компиляции он становится object_msgSend (target, @ selector (doMethodWith :) , var1). Понимание принципа работы механизма выполнения может помочь вам глубже понять язык Objective-C и рабочий процесс приложения, которое вы пишете. Я считаю, что разработчики Mac / iPhone на всех уровнях получат кое-что в процессе изучения механизма исполнения.

Библиотека времени выполнения Objective-C с открытым исходным кодом

Библиотека времени выполнения Objective-C имеет открытый исходный код, вы всегда можетеИсходный кодПроверить. Фактически, просмотр исходного кода - один из предпочтительных способов понять принципы Objective-C, а не чтение документов Apple по разработке. Загрузите последнюю версию исходного коданажми на меня。

Динамический и статический язык

Приведенный выше код компилируется и оптимизируется компилятором, а затем оптимизированный код преобразуется в язык ассемблера:

Затем свяжите соответствующую библиотеку, чтобы сгенерировать исполняемый файл. По сравнению с Objective-C, хотя процесс обработки кода очень похож, скомпилированный код зависит от библиотеки времени выполнения Objective-C. Когда мы впервые изучили Objective-C, нам сказали, как обрабатывается код в скобках, как показано ниже.

Кроме того, мы действительно не знаем, как работает механизм времени выполнения, и, возможно, мы узнаем это через долгое время.

Что такое Runtime (время выполнения)

Objective-C Runtime - это библиотека времени выполнения, в основном написанная на языке C и ассемблере. После добавления объектно-ориентированных функций на основе языка C она становится языком Objective-C. Это означает, что механизм времени выполнения отвечает за такие операции, как загрузка классов, отправка методов и доставка методов. По сути, механизм времени выполнения предоставляет всю необходимую структуру для поддержки объектно-ориентированного программирования Objective-C.

Терминология времени выполнения Objective-C

Прежде чем идти дальше, давайте очистим некоторые терминологические барьеры, чтобы мы оказались в том же положении. Что касается разработчиков приложений для MacOS X и iPhone OS, существует два механизма выполнения: Modern Runtime и Legacy Runtime. Modern Runtime подходит для всех 64-битных приложений MacOS и всех приложений iPhone, а Legacy Runtime подходит для всех 32-битных приложений MacOS. В механизме выполнения есть два типа функций: функции экземпляра (начинающиеся с символа «-», например - (void) doFoo); функции класса (начинающиеся с символа «+», например + (id) alloc). Обе функции очень похожи на функцию C и содержат набор кодов для достижения определенной задачи, как показано ниже.

Селектор: в Objective-C селектор - это, по сути, структура данных C, используемая для идентификации функции, которую будет выполнять объект. Определение в механизме выполнения выглядит следующим образом

IMP - это указатель на функцию, который генерируется компилятором и указывает на содержимое реализации функции. Если вы в настоящее время новичок в Objective-C, вы можете просто попробовать его на вкус, но мы узнаем, как механизм времени выполнения вызывает ваши функции позже.

Класс Objective-C: что внутри класса? В Objective-C реализация класса в основном похожа на:

Но определение класса в механизме выполнения далеко не так, как следует

Класс определяет объект, и сам класс также является объектом? Почему?

Зачем наследовать собственный класс Apple?

Когда вы впервые начали программировать в Какао, все соответствующие учебники говорили о создании класса, наследуемого от NSObject, а затем о начале написания собственного кода.Простое наследование от собственных классов Apple принесет вам большую пользу. Одно из преимуществ, о котором вы даже не подозреваете, заключается в том, что создаваемые вами классы работают в механизме выполнения. Когда мы создаем новый объект-экземпляр, как показано ниже:

Механизм кэширования классов

Что произойдет дальше:

В этом примере класса NSObject особых точек не отображается. Но это не всегда так. Иногда важна инициализация, например:

Если вы новичок в Какао, то я прошу вас распечатать результат приведенного выше кода, ваш ответ может быть следующим:

Но результат бега:

Это связано с тем, что в Objective-C вызов + alloc неявно вернет объект-экземпляр одного класса, а вызов -init вернет объект-экземпляр другого класса.

Каков рабочий процесс objc_msgSend?

Реализация функции objc_msgSend более сложна. Например, мы написали следующий код .

Приведенный выше код будет фактически преобразован компилятором в:

Затем функция objc_msgSend запрашивает соответствующий класс (или любой родительский класс) в соответствии с указателем isa целевого объекта, чтобы узнать, отвечает ли он на селектор @selector (printMessageWithString :). Предполагая, что соответствующая реализация функции находится в списке диспетчеризации функций или в кеше класса, функция выполняется. Похоже, что функция objc_msgSend не имеет возвращаемого значения. Она начинает выполнение, а затем находит соответствующую целевую функцию и выполняет ее. Поэтому возвращаемое значение целевой функции рассматривается как возвращаемое значение функции objc_msgSend.

Исследование Билла Бумгарнера по objc_msgSend является более глубоким, чем я хочу выразить (part 1,part 2,part 3). Обобщите то, что он хочет выразить и что вы можете найти, обратившись к исходному коду механизма выполнения:

Будет скомпилирован в:

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

Таким образом, вы можете получить доступ к этим функциям и вызывать их непосредственно во время выполнения, и даже использовать этот метод для обхода динамических вызовов во время выполнения, чтобы гарантировать выполнение указанной функции. Механизм выполнения также может вызывать вашу функцию, но через objc_msgSend ().

  1. Механизм выполнения искал класс объекта и списки кешей и списки функций во всех его родительских классах, но не нашел указанный метод.
  2. Затем механизм выполнения вызовет метод + (BOOL) resolveInstanceMethod: (SEL) aSEL в вашем классе, чтобы предоставить вам возможность предоставить реализацию функции для указанной функции и сообщить механизму времени выполнения, что вы реализовали этот метод. Если механизм времени выполнения снова ищет эту функцию, можно найти соответствующую реализацию функции. Вы можете реализовать эту функцию следующим образом:

Как показано ниже, используйте class_addMethod () для достижения

Последний параметр "v @:" class_addMethod () представляет возвращаемое значение и параметры функции fooMethod. Вы можете ввести код в руководстве по механизму времени выполнения.Type EncodingsВы можете понять конкретные правила.

  1. Затем механизм выполнения вызовет функцию- (id) forwardingTargetForSelector: (SEL) aSelector, что даст вам возможность указать среде выполнения на другой объект, который может реагировать на целевую функцию. Это дороже, чем запуск функций: - (void) forwardInvocation: (NSInvocation *) anInvocation более рентабельно. Ваша конкретная реализация может выглядеть так:

Очевидно, вы не хотите возвращать указатель на себя, иначе это может вызвать бесконечный цикл.

Нефрагментация переменных экземпляра (современная среда выполнения)

Одна из концепций, которые мы узнали о Modern Runtime, состоит в том, что переменные экземпляра не фрагментируются (Non Fragile ivars). Компилятор определяет структуру переменных экземпляра при компиляции класса и определяет место доступа к переменной экземпляра. Это низкоуровневая деталь, которая касается получения указателя на объект, нахождения смещения переменной экземпляра относительно начальной позиции объекта и чтения соответствующего количества байтов в соответствии с типом переменной экземпляра. Следовательно, макет переменной экземпляра может выглядеть следующим образом, а число слева представляет байтовое смещение переменной экземпляра.

Как показано выше, макет переменной экземпляра объекта NSObject и макет после наследования NSObject и добавления собственных переменных. Этот макет обычно работает до того, как Apple выпустит обновление, но после того, как Apple выпустит Mac OS X 10.6, макет будет следующим:

Поскольку она перекрывается с переменной экземпляра родительского класса, переменная экземпляра настраиваемого объекта стирается. Единственный способ предотвратить это - сохранить макет перед обновлением. Но если Apple сделает это, фреймворк Apple будет невозможно улучшить, потому что макет переменных экземпляра этих фреймворков был написан до смерти. В случае фрагментации переменных экземпляра единственный способ обеспечить совместимость с новой структурой - это перекомпилировать все классы, унаследованные от класса Apple. Итак, что произойдет, если переменные экземпляра не будут фрагментированы?

Исходя из того, что переменная экземпляра не фрагментирована, компилятор создает тот же макет переменной экземпляра, что и в случае фрагментации переменной экземпляра. Но когда во время выполнения обнаруживается перекрывающийся родительский класс, смещение настраиваемой переменной корректируется, поэтому настраиваемая переменная в подклассе сохраняется.

Объекты, связанные с Objective-C

Недавно в Mac OS X 10.6 Snow Leopard появилась новая функция, называемая ассоциативными ссылками. В отличие от некоторых других языков, Objective-C не поддерживает динамическое добавление переменных экземпляра в класс объекта. Итак, перед этим вы должны истощить свой мозг, чтобы построить определенную инфраструктуру, создав иллюзию, что вы можете динамически добавлять переменные к объекту. Теперь в Mac OS X 10.6 среда выполнения уже поддерживает эту функцию. Если вы хотите добавить переменную в любой существующий собственный класс Apple, такой как NSView, мы можем сделать следующее:

Вы можете увидеть параметры для хранения связанных объектов в заголовочном файле runtime.h, которые передаются в качестве параметра функции objc_setAssociatedObject ().

Эти необязательные значения соответствуют необязательным значениям синтаксиса @property.

Hybrid vTable Dispatch (Отправка гибридных vTable)

Если вы посмотрите исходный код современной версии среды выполнения, вы увидите следующее (Находится по адресу objc-runtime-new.m):

Суть вышеупомянутого контента заключается в том, что наиболее часто вызываемые функции будут сохраняться как можно больше во время выполнения для достижения цели повышения скорости работы программного обеспечения, поскольку при поиске по таблице виртуальных функций используется меньше инструкций, чем при вызове objc_msgSend функция. 16 вызовов функций в таблице виртуальных функций выполняются гораздо чаще, чем все другие функции. Фактически, если вы дополнительно изучите код, вы найдете функции по умолчанию в таблице виртуальных функций под механизмом сборки мусора и без механизма сборки мусора:

Итак, как узнать, вызываются ли эти функции? В режиме отладки вы увидите, что в стеке вызывается одна из следующих функций: В целях отладки все эти методы можно рассматривать как вызываемые через функцию objc_msgSend.

  1. objc_msgSend_fixup: инициируется, когда функция в таблице виртуальных функций отправляется во время выполнения, то есть используется для отправки функции в таблице виртуальных функций.
  2. objc_msgSend_fixedup: он запускается, когда функция, которая должна существовать в таблице виртуальных функций, вызывается, но больше не существует (я лично считаю, что она должна вызываться после функции objc_msgSend_fixup и запускаться первой).
  3. objc_msgSend_vtable 11: в режиме отладки вы можете увидеть, что вызов функции, подобный objc_msgSend_vtable5, означает, что вызывается функция соответствующего серийного номера в таблице виртуальных функций.

Среда выполнения может решить, следует ли отправлять эти функции, поэтому не ожидайте возникновения следующей ситуации: objc_msgSend_vtable10 соответствует -length в цикле во время выполнения, что означает, что такая же ситуация в любом последующем цикле.

в заключении

Надеюсь, вам понравится содержание, эта статья в основном охватывает моиDes Moines Cocoaheads Обсудили по содержанию. Среда выполнения Objective-C - потрясающий шедевр. Она обеспечивает мощную платформу для наших приложений Cocoa / Objective-C, делая возможными многие из используемых нами функций. Если вы не проверили документацию Apple по разработке о том, как использовать среду выполнения Objective-C, я надеюсь, вы примете меры немедленно, спасибо. прикреплять:Документация по разработке во время выполнения,Документация во время выполнения

В этой статье основное внимание будет уделено описанию принципов работы Objective-C Runtime и деталям внутреннего устройства этого языка, что в будущем поможет проводить более глубокий и качественный анализ безопасности IOS-приложений.

image

Автор: Пратик Джианчандани (Prateek Gianchandani)

Почти все «родные» IOS-приложения написаны на Objective-C. Все эти приложения используются библиотеку Cocoa, содержащую набор высокоуровневых API-функций, которые заметно упрощают разработку приложений под Mac и IOS. Также в Cocoa есть среда выполнения (runtime environment) для приложений. В этой статье основное внимание будет уделено описанию принципов работы Objective-C Runtime и деталям внутреннего устройства этого языка, что в будущем поможет проводить более глубокий и качественный анализ безопасности IOS-приложений.

Objective-C Runtime

Objective-C представляет собой динамически ориентированный язык (runtime-oriented language). При этом возникает закономерный вопрос, что же такое динамический язык (runtime language)? Динамический язык – это такой язык, когда все решения (в том числе и при вызове функций) принимаются во время выполнения приложений. Является ли Objective-C динамическим языком? Ответ: нет. Objective-C – динамически ориентированный язык, а это означает, что принятие решения во время выполнения приложения происходит только там, где это возможно. Как говорилось ранее, библиотека Cocoa предоставляет среду выполнения, которая необходима IOS-приложениям. На рисунке ниже приводится параграф из документации Apple, проясняющий многие вещи.


Рисунок 1: Выдержка из документации Apple для языка Objective-C

Перевод текста с Рисунка 1: Приложения, написанные на языке Objective-C, принимают решения во время выполнения везде, где это возможно (то есть происходит динамическое исполнение кода). Это означает, что для работоспособности приложения требуется не только компилятор, но и среда выполнения, которая предназначена для запуска скомпилированного кода. Для языка Objective-C среда выполнения функционирует как операционная система, которая отвечает за работоспособность приложений, написанных на Objective-C.

Теперь проверим, импортируется ли динамическая библиотека (runtime library) внутри проектов или нет. В идеале импорт должен происходить в каждом IOS-приложении. Чтобы проверить это, подключитесь к устройству и зайдите в директорию с приложениями.


Рисунок 2: Содержимое директории /var/mobile/Applications

Теперь введите «ls *» для вывода списка всех директорий, включая поддиректории.


Рисунок 3: Содержимое директории /var/mobile/Applications вместе с поддиректориями каждой директории

Рассмотрим содержимое директории приложения BADLAND (весьма популярная игра для IOS). Заходим внутрь директории BADLAND.app и смотрим бинарный файл Badland при помощи утилиты otool.


Рисунок 4: Отображение содержимого бинарного файла при помощи утилиты otool

Из рисунка выше мы видим, что импортируется достаточно большое количество библиотек. Импорт библиотеки objc-runtime показано на рисунке ниже.


Рисунок 5: Импорт библиотеки objc-runtime

Библиотека objc-runtime делает возможным манипуляцию кодом во время выполнения программы, написанной на Objective-C. По умолчанию, эта библиотека используется во всех IOS-приложениях. К примеру, рассмотрим приложение Google Maps для платформы IOS (также при помощи утилиты otool).


Рисунок 6: Анализ бинарного файла приложения Google Maps при помощи otool

Как видно из Рисунка 6, в приложении Google Maps также происходит импорт библиотеки Objective-C Runtime.

Динамический анализ приложения при помощи GDB

В этом разделе мы рассмотрим техники анализа во время выполнения приложения при помощи GDB. Первым делом необходимо установить корректную версию этого отладчика. Та версия, которая доступна через Cydia, не работает, и вам необходимо загрузить бинарный файл из других источников. После этого при помощи sftp загрузите файл gdb на устройство, как показано на рисунке ниже.


Рисунок 7: Процедура загрузки бинарного файла на устройство при помощи sftp

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


Рисунок 8: Установка прав, необходимых для запуска отладчика

Чтобы подцепиться к запущенному процессу, необходимо вначале убедиться, что процесс запущен. Мы будем проводить тестирование на приложении Google Maps. Вначале запустим это приложение на устройстве и узнаем идентификатор процесса. Кроме того, необходимо убедиться в том, что приложение работает на переднем плане (foreground). Как видно из рисунка ниже, идентификатор процесса приложения Google Maps – 661 (в вашем случае идентификатор может быть другим).


Рисунок 9: Выяснение идентификатора процесса приложения Google Maps

Теперь подцепимся к процессу при помощи GDB.



Рисунок 10: Подцепляемся к процессу с идентификатором 661 при помощи GDB


Рисунок 11: Устанавливаем точку останова на метод objc_msgSend (break objc_msgSend) и команды, который выполняются во время срабатывания точки останова (x/a $r0 – печать $r0; x/a $r1 – печать $r1, c – продолжить выполнение приложения)


Рисунок 12: Информация, выдаваемая отладчиком, во время выполнения приложения

Как видно из Рисунка 12, эта техника хорошо помогает при анализе логики работы приложения. Повторюсь еще раз, возможно, конкретно в данном случае столь огромный объем информации может быть бесполезен во время настоящего анализа. Все это продемонстрировано лишь с целью показать вам, как много информации мы можем получить относительно реализации приложения.

Method Swizzling

Мы уже знаем, что все IOS-приложения во время запуска используют среду выполнения. Это означает, что многие решения принимаются во время выполнения приложения. Method Swizzling – еще одно оружие, которое мы можем использовать для модификации поведения IOS-приложения. Эта техника позволяет нам переопределить логику работы метода. Мы рассмотрим более подробно Method Swizzling в следующей статье.

Objective-C задумывался как надстройка над языком C, добавляющая к нему поддержку объектно-ориентированной парадигмы. Фактически, с точки зрения синтаксиса, Objective-C — это достаточно небольшой набор ключевых слов и управляющих конструкций над обычным C. Именно Runtime, библиотека времени выполнения, предоставляет тот набор функций, которые вдыхают в язык жизнь, реализуя его динамические возможности и обеспечивая функционирование ООП.

Базовые структуры данных

Функции и структуры Runtime-библиотеки определены в нескольких заголовочных файлах: objc.h , runtime.h и message.h . Сначала обратимся к файлу objc.h и посмотрим, что представляет из себя объект с точки зрения Runtime:


Мы видим, что объект в процессе работы программы представлен обычной C-структурой. Каждый Objective-C объект имеет ссылку на свой класс — так называемый isa-указатель. Думаю, все видели его при просмотре структуры объектов во время отладки приложений. В свою очередь, класс также представляет из себя аналогичную структуру:


Класс в Objective-C — это полноценный объект и у него тоже присутствует isa-указатель на «класс класса», так называемый метакласс в терминах Objective-C. Аналогично, С-структуры определены и для других сущностей языка:

Функции Runtime-библиотеки
  • Манипулирование классами: class_addMethod , class_addIvar , class_replaceMethod
  • Создание новых классов: class_allocateClassPair , class_registerClassPair
  • Интроспекция: class_getName , class_getSuperclass , class_getInstanceVariable , class_getProperty , class_copyMethodList , class_copyIvarList , class_copyPropertyList
  • Манипулирование объектами: objc_msgSend , objc_getClass , object_copy
  • Работа с ассоциативными ссылками
Пример 1. Интроспекция объекта

Рассмотрим пример использования Runtime библиотеки. В одном из наших проектов модель данных представляет собой plain old Objective-C объекты с некоторым набором свойств:


Для удобства отладки хотелось бы, чтобы при выводе в лог печаталась информация о состоянии свойств объекта, а не нечто вроде <COConcreteObject: 0x71d6860> . Поскольку модель данных достаточно разветвленная, с большим количеством различных подклассов, нежелательно писать для каждого класса отдельный метод description , в котором вручную собирать значения его свойств. На помощь приходит Objective-C Runtime:


Метод, определенный в общем суперклассе объектов модели, получает список всех свойств объекта с помощью функции class_copyPropertyList . Затем значения свойств собираются в NSDictionary , который и используется при построении строкового представления объекта. Данный алгоритм раработает только со свойствами, которые являются Objective-C объектами. Проверка типа осуществляется с использованием функции property_getAttributes . Результат работы метода выглядит примерно так:

2013-05-04 15:54:01.992 Test[40675:11303] COConcreteObject: name = Foo;
quantity = 10;
title = bar;
>


Вызов objc_msgSent инициирует процесс поиска реализации метода, соответствующего селектору, переданному в функцию. Реализация метода ищется в так называемой таблице диспетчеризации класса. Поскольку этот процесс может быть достаточно продолжительным, с каждым классом ассоциирован кеш методов. После первого вызова любого метода, результат поиска его реализации будет закеширован в классе. Если реализация метода не найдена в самом классе, дальше поиск продолжается вверх по иерархии наследования — в суперклассах данного класса. Если же и при поиске по иерархии результат не достигнут, в дело вступает механизм динамического поиска — вызывается один из специальных методов: resolveInstanceMethod или resolveClassMethod . Переопределение этих методов — одна из последних возможностей повлиять на Runtime:

Пример 2. Method Swizzling

Одна из особенностей категорий в Objective-C — метод, определенный в категории, полностью перекрывает метод базового класса. Иногда нам требуется не переопределить, а расширить функционал имеющегося метода. Пусть, например, по каким-то причинам нам хочется залогировать все добавления элементов в массив NSMutableArray . Стандартными средствами языка этого сделать не получится. Но мы можем использовать прием под названием method swizzling:


Мы перегружаем метод load — это специальный callback, который, если он определен в классе, будет вызван во время инициализации этого класса — до вызова любого из других его методов. Здесь мы меняем местами реализацию базового метода addObject: и нашего метода logAddObject: . Обратите внимание на «рекурсивный» вызов в logAddObject: — это и есть обращение к перегруженной реализации основного метода.

Пример 3. Ассоциативные ссылки

Еще одним известным ограничением категорий является невозможность создания в них новых переменных экземпляра. Пусть, например, вам требуется добавить новое свойство к библиотечному классу UITableView — ссылку на «заглушку», которая будет показываться, когда таблица пуста:


«Из коробки» этот код работать не будет, вы получите исключение во время выполнения программы. Эту проблему можно обойти, используя функционал ассоциативных ссылок:


Любой объект вы можете использовать как ассоциативный массив, связывая с ним другие объекты с помощью функции objc_setAssociatedObject . Для ее работы требуется ключ, по которому вы потом сможете извлечь нужный вам объект назад, используя вызов objc_getAssociatedObject . При этом вы не можете использовать скопированное значение ключа — это должен быть именно тот объект (в примере — указатель), который был передан в вызове objc_setAssociatedObject .


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

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

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

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

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

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

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

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

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

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

Objective-C задумывался как надстройка над языком C, добавляющая к нему поддержку объектно-ориентированной парадигмы. Реально, с точки зрения синтаксиса, Objective-C — это довольно маленький комплект ключевых слов и руководящих конструкций над обыкновенным C. Именно Runtime, библиотека времени выполнения, предоставляет тот комплект функций, которые вдыхают в язык жизнь, реализуя его динамические вероятности и обеспечивая функционирование ООП.

Базовые конструкции данных

Функции и конструкции Runtime-библиотеки определены в нескольких заголовочных файлах: objc.h , runtime.h и message.h . Вначале обратимся к файлу objc.h и посмотрим, что представляет из себя объект с точки зрения Runtime:

Мы видим, что объект в процессе работы программы представлен обыкновенной C-конструкцией. Всякий Objective-C объект имеет ссылку на свой класс — так называемый isa-указатель. Думаю, все видели его при просмотре конструкции объектов во время отладки приложений. В свою очередь, класс также представляет из себя аналогичную конструкцию:

Класс в Objective-C — это полновесный объект и у него тоже присутствует isa-указатель на «класс класса», так называемый метакласс в терминах Objective-C. Подобно, С-конструкции определены и для других сущностей языка:

Функции Runtime-библиотеки

Помимо определения основных конструкций языка, библиотека включает в себя комплект функций, работающих с этими конструкциями. Их дозволено условно поделить на несколько групп (предназначение функций, как правило, видимо из их наименования):

  • Манипулирование классами: class_addMethod , class_addIvar , class_replaceMethod
  • Создание новых классов: class_allocateClassPair , class_registerClassPair
  • Интроспекция: class_getName , class_getSuperclass , class_getInstanceVariable , class_getProperty , class_copyMethodList , class_copyIvarList , class_copyPropertyList
  • Манипулирование объектами: objc_msgSend , objc_getClass , object_copy
  • Работа с ассоциативными ссылками
Пример 1. Интроспекция объекта

Разглядим пример применения Runtime библиотеки. В одном из наших планов модель данных представляет собой plain old Objective-C объекты с некоторым комплектом свойств:

Для комфорта отладки хотелось бы, Дабы при итоге в лог печаталась информация о состоянии свойств объекта, а не что-то как бы <COConcreteObject: 0x71d6860> . От того что модель данных довольно разветвленная, с огромным числом разных подклассов, неугодно писать для всякого класса обособленный способ description , в котором вручную собирать значения его свойств. На поддержка приходит Objective-C Runtime:

Способ, определенный в всеобщем суперклассе объектов модели, получает список всех свойств объекта с поддержкой функции class_copyPropertyList . После этого значения свойств собираются в NSDictionary , тот, что и применяется при построении строкового представления объекта. Данный алгорифм раработает только со свойствами, которые являются Objective-C объектами. Проверка типа осуществляется с применением функции property_getAttributes . Итог работы способа выглядит приблизительно так:

2013-05-04 15:54:01.992 Test[40675:11303] COConcreteObject: name = Foo;
quantity = 10;
title = bar;
>

Вызов objc_msgSent инициирует процесс поиска реализации способа, соответствующего селектору, переданному в функцию. Реализация способа ищется в так называемой таблице диспетчеризации класса. От того что данный процесс может быть довольно продолжительным, с всяким классом ассоциирован кеш способов. Позже первого вызова всякого способа, итог поиска его реализации будет закеширован в классе. Если реализация способа не обнаружена в самом классе, дальше поиск продолжается вверх по иерархии наследования — в суперклассах данного класса. Если же и при поиске по иерархии итог не достигнут, в дело вступает механизм динамического поиска — вызывается один из особых способов: resolveInstanceMethod либо resolveClassMethod . Переопределение этих способов — одна из последних вероятностей повлиять на Runtime:

Пример 2. Method Swizzling

Одна из особенностей категорий в Objective-C — способ, определенный в категории, всецело перекрывает способ базового класса. Изредка нам требуется не переопределить, а расширить функционал имеющегося способа. Пускай, скажем, по каким-то причинам нам хочется залогировать все добавления элементов в массив NSMutableArray . Стандартными средствами языка этого сделать не получится. Но мы можем применять прием под наименованием method swizzling:

Мы перегружаем способ load — это особый callback, тот, что, если он определен в классе, будет вызван во время инициализации этого класса — до вызова всякого из других его способов. Тут мы меняем местами реализацию базового способа addObject: и нашего способа logAddObject: . Обратите внимание на «рекурсивный» вызов в logAddObject: — это и есть обращение к перегруженной реализации основного способа.

Пример 3. Ассоциативные ссылки

Еще одним вестимым лимитацией категорий является неосуществимость создания в них новых переменных экземпляра. Пускай, скажем, вам требуется добавить новое качество к библиотечному классу UITableView — ссылку на «заглушку», которая будет показываться, когда таблица пуста:

«Из коробки» данный код трудиться не будет, вы получите исключение во время выполнения программы. Эту загвоздку дозволено обойти, применяя функционал ассоциативных ссылок:

Всякий объект вы можете применять как ассоциативный массив, объединяя с ним другие объекты с поддержкой функции objc_setAssociatedObject . Для ее работы требуется ключ, по которому вы потом сумеете извлечь необходимый вам объект назад, применяя вызов objc_getAssociatedObject . При этом вы не можете применять скопированное значение ключа — это должен быть именно тот объект (в примере — указатель), тот, что был передан в вызове objc_setAssociatedObject .

Завершение

Сейчас вы располагаете базовым представлением о том, что такое Objective-C Runtime и чем он может быть пригоден разработчику на практике. Для желающих узнать вероятности библиотеки глубже, могу порекомендовать следующие добавочные источники:

Читайте также: