- PVSM.RU - https://www.pvsm.ru -
(оригинал — Mike Ash, взято отсюда [1])
Многие Cocoa разработчики имеют довольно смутное представление об Objective-C Runtime API. Они знают, что он существует где-то там(некоторые не знают даже этого!), что он важен, и Objective-C без него неработоспособен, но обычно этим все знания и ограничиваются.
Сегодня я расскажу о том, как устроен Objective-C на уровне Runtime и о том, как конекретно вы можете это использовать.
В Objective-C мы постоянно имеем дело с объектами, но что же такое объект на самом деле? Давайте попробуем соорудить что-то, что поможет пролить нам свет на этот вопрос.
Во-первых, всем нам известно, что мы ссылаемся на объекты с помощью указателей, например, NSObject *. Также мы знаем, что создаём мы их с помощью +alloc. Всё, что мы може узнать об этом из документации, так это то, что это происходит путём вызова +allocWithZone:. Продолжая нашу цепочку исследований, мы обнаруживаем NSDefaultMallocZone, который создаётся с помощью обыкновенного malloc. И всё!
Но что из себя представляют созданные объекты? Что ж, посмотрим:
#import <Foundation/Foundation.h>
@interface A : NSObject { @public int a; } @end
@implementation A @end
@interface B : A { @public int b; } @end
@implementation B @end
@interface C : B { @public int c; } @end
@implementation C @end
int main(int argc, char **argv)
{
[NSAutoreleasePool new];
C *obj = [[C alloc] init];
obj->a = 0xaaaaaaaa;
obj->b = 0xbbbbbbbb;
obj->c = 0xcccccccc;
NSData *objData = [NSData dataWithBytes:obj length:malloc_size(obj)];
NSLog(@"Object contains %@", objData);
return 0;
}
Мы соорудили иерархию классов, каждый из которых содержет в себе переменные, и заполнили их вполне очивидными значениями. Затем мы извлекли данные в удобоваримый вид, исползьуя malloc_size, дабы получить правильную длину и воспользовались NSData, чтобы распечатать всё в hex. Вот, что у нас получилось на выходе:
2009-01-27 15:58:04.904 a.out[22090:10b] Object contains <20300000 aaaaaaaa bbbbbbbb cccccccc>
Мы видим, что класс последовательно заполнил ячейки памяти—сначала переменную A, потом его наследника B, а потом C. Всё просто!
Но что за 20300000 в самом начале? Они идут перед A, и потому, скорее всего, принадлежат NSObject. Посмотрим-ка на определение NSObject.
/*********** Base class ***********/
@interface NSObject {
Class isa;
}
Как видим, вновь какая-то пременная. Но что это за Class такой? Переходим по определению, которое нам предлагает Xcode и попадаем в usr/include/objc/objc.h, в котором находим следущее:
typedef struct objc_class *Class;
Идём дальше в /usr/include/objc/runtime.h и видим:
struct objc_class {
Class isa;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
Таким образом, Class это указатель на структуру, которая… Начинается с ещё одного Class
Посмотрим ещё один класс, NSProxy
@interface NSProxy {
Class isa;
}
И тут он есть. Ещё один, id, за которым может скрываться любой объект в Objective-C
typedef struct objc_object {
Class isa;
} *id;
И снова он. Очевидно, что каждый объект в Objective-C должен начинаться с Class isa, даже объекты классов. Так что же это такое?
Как следует из названия и типа, переменная isa указывает, какому классу принадлежит тот или иной объект. Каждый объект в Objective-C должен начинаться с isa, иначе runtime не будет знать, что же с ним делать. Вся информация о типе каждого конкретного объекта скрывается за этим крохотным указателем. Оставшийся кусок объекта, с точки зрения runtime, представляет из себя просто огромный BLOB, не дающий никакой информации. Только лишь классы могут придать этому куску какой-то смысл.
Что же тогда на самом деле содержится в классах? Ответ на этот вопрос нам поможет найти «недоступные » поля структуры (те, что после #if !__OBJC2__, они оставлены здесь для совместимости с пре-Леопардом, и вы не должны пользоваться ими, если занимаеться разработкой пол Леопард и выше, однако, они помогут понять нам, что же за информация там скрывается). Сначала идет isa, позволяющий работать с классом, как с объектом. Потом идет ссылка на Class — предок, дабы не нарушалась иерархия классов. Далее идет некоторая базовая информация о классе. Но самое интересное — в конце. Это список переменных, список методов и список протоколов. Все это доступно во время исполнения, и может быть изменено там же!
Я пропустил кэш, так как он не слишком интересен с точки зрения манипуляции во время исполнения, но стоит рассказать о том, какую роль он играет в принципе. Каждый раз, когда вы посылаете сообщение ([foo bar]), Runtime ищет его в списке методов класса объекта. Но так как это просто линейный список, этот процесс достаточно продолжительный. Кэш же — это хэш таблица, в которой содержатся уже вызывавшиеся до этого методы. Именно поэтому первый вызов метода может быть значительно дольше, чем все последующие.
Исследуя runtime.h, вы можете обнаружить множество функций для доступа и изменению этих элементов. Каждая функция начинается с префикса, который показывает, с чем она имеет дело. Базовые начинаются на objc_, функции для работы с классами на class_, и так далее. Например, вы можете вызвать class_getInstanceMethod, чтобы узнать информацию о конкретном методе, такую как список аргументов/тип возвращаемого значения. Или же можете добавить новый метод с помощью class_addMethod. Вы даже можете создавать целые классы с помощью objc_allocateClassPair прямо во время исполнения!
Есть множество вариантов применеия этой Runtime мета-информации, вот только некоторые из них
Objective-C — это мощный язык, ключевую роль в динамичности которого играет всеохватывающий Runtime API. Быть может, не так уж и приятно возиться со всем этим C кодом, но возможности, которые он открывает, поистине огромны.
(прим. переводчика — для тех, кому уже не терпится поиграться со всей этой бесконечной динамичностью Objective-C, но не хочется разбираться с runtime.h, Mike Ash выложил на GitHub [3] проект — обертку над runtime.h, предоставляющую полный доступ ко всем вкусностям, описаным выше, но в привычном Objective-C синтаксисе.)
Автор: pestrov
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/objective-c/12579
Ссылки в тексте:
[1] отсюда: http://www.mikeash.com/pyblog/friday-qa-2009-03-13-intro-to-the-objective-c-runtime.html
[2] Better key-value observing for Cocoa: http://www.mikeash.com/pyblog/key-value-observing-done-right.html
[3] выложил на GitHub: https://github.com/mikeash/MAObjCRuntime
Нажмите здесь для печати.