Pull to refresh

Comments 31

Программист на фортране — на любом языке может писать на фортране?

Я ни в коем случае не хочу обидеть язык Си, однако в данном случае я не вижу смысла в происходящем.
Селектор является «строкой» лишь постольку поскольку. Селектор является абстракцией, указателем в памяти, для удобства, помимо прочего, указывающую на некую описывающую его структуру (в данном случае С-стринг). С большего, этого знания достаточно для работы с этой частью языка.

Поэтому не дай вам бог использовать это знание в своих проектах.

P.S. К слову, могу порекомендовать закопаться на какое-то время в этот репозиторий Там все есть. Кроме определения SEL — потому что это определение кодогенерируемое :-)

objc_selector:

typedef struct objc_selector *SEL;
Эта штука имеет обратное значение. objc_selector — является алиасом на *SEL, а не наоборот
Хм, прошу прощения за последнюю ремарку — она ошибочна.
Ничего страшного, Вы еще и ссылку дали на тот самый репозиторий, на который я и ссылался в статье :). А реализации действительно разные.
Меня, честно говоря, совершенно не интересует применение всего мной описанного в реальных проектах, потому что на работе я просто перемещаю формочки по Storyboard.

Я погрузился в изучение Runtime только из-за спортивного интереса. Меня всю жизнь привлекали низкоуровневые вещи, поэтому изучение runtime приносит мне удовольствие. Кто-то марки коллекционирует, кто-то футбол смотрит, а я низкоуровневое программирование люблю.

За замечание про objc_selector — спасибо. Только это тема, наверное, отдельной статьи, потому что в каждой реализации Objective-C Runtime этот тип представлен по-разному. Например, вот здесь он действительно является структурой.
Ну с этой точки зрения реализация сущности сообщений, а, главное, их смысл — это действительно жемчужина objective-C, которая позволяет реализовать много действительно красивых и изящных вещей.
UFO just landed and posted this here
Отличная статья, надеюсь на продолжение! Язык действительно классный, несмотря на необычный синтаксис. Классный именно своими динамическими возможностями.
Я как-то смотрел исходники рантайма и проводил небольшие эксперименты, с похожей целью — выяснить как же работает этот механизм отправки сообщений. Селекторы, сформированные на этапе компиляции, имеют значения, сильно отличающиеся от селекторов этапа выполнения (видимо, это связано с тем, что существует две таблицы — статическая в секции константных данных и динамическая в куче).
Очень интересно, как селектор связывается с методом, сравнительный анализ этого с таблицами виртуальных функций (и можно ли эти два механизма объединить), реализация возможностей динамического добавления (и удаления?) методов из классов и т.д.
Продолжение обязательно будет, как и ответы на поставленные Вами вопросы. Как говорил Шеф из Саус Парка: «Всему своё время, и время это — колледж» :).
Как это сильно отличаются? Селектор это строка и ничего более. Никакого name mangling у objc нет. Все скомпилированные методы классов в бинарнике попадают в секцию, где лежат в виде обычных строк. Уже в другой секции есть списки методов со ссылками на этим строки. Так же рядышком лежат type encoding для каждого метода с описанием типов аргументов. Как эти методы были написаны программистом, так они и попадают в скомпилированный код, без единого изменения. Разве что из-за type encoding теряется информация о типах.
Собственно, если бы что-то сильно отличалось, то были бы проблемы с твиками для джейлбрейка. В твиках мы обычные строки конвертируем в селекторы не имея на руках даже прототипов классов. Просто берется С строка вида «initWithKey:value:» и передается рантайму, чтобы он уже подменял реальный метод с таким селектором нашей реализацией. Мы сами знать не знаем ни о каких хедерах и классах с таким методом, компилятор ничего о них не знает.

Да, добавлять и подменять в рантайме методы можно и это очень и очень просто. Вот для удаления API нет.
Отличаются в смысле самих числовых значений селекторов.
Вообще конечно это зависит от компилятора. Я смотрю на виртуалке Snow Leopard, там так. Попробовал сейчас на ideone.com — там иначе.
Идея следующая. Создаем objc-класс с парой методов.
@interface c1 : NSObject {}
- (int) meth1 : (int) arg;
- (int) meth2 : (int) arg;
@end

@implementation c1
- (int) meth1 : (int) arg { return arg+2; }
- (int) meth2 : (int) arg { return arg+20; }
@end


Далее, фукнция печати селектора
void print_sel(SEL s)
{
	printf("SEL %08X = %s\n", s, s);
}


И код main:
int main()
{
	c1 *obj1 = [[c1 alloc] init];
	SEL s1 = @selector(meth1);
	SEL s2 = @selector(meth2);
	SEL s3 = @selector(methUnexist1);
	
	SEL s4 = NSSelectorFromString(@"meth1");
	SEL s5 = NSSelectorFromString(@"meth2");
	SEL s6 = NSSelectorFromString(@"methUnexist2");

	SEL s6 = NSSelectorFromString(init);	

	print_sel(s1);
	print_sel(s2);
	print_sel(s3);
	print_sel(s4);
	print_sel(s5);
	print_sel(s6);
	print_sel(s7);	

	return 0;
}


В результате имеем: s1 и s2 — селекторы существующих пользовательских методов; s3 — селектор несуществующего метода, но оформленный конструкцией языка selector; s4 и s5 — селекторы существующих пользовательских методов через функцию получения по строке (в рантайме); s6 — селектор несуществующего метода по строке в рантайме; s7 — селектор системного метода.

Вывод (Snow Leopard):
SEL 00000DEB = meth1
SEL 00000DF1 = meth2
SEL 00000E20 = methUnexist1
SEL 00000DEB = meth1
SEL 00000DF1 = meth2
SEL 0010FF60 = methUnexist2
SEL 816CD218 = init


Видим, что селекторы существующих методов (добавленные на этапе компиляции) совпадают для разных способов получения селектора (оператором @ selector или рантаймовой фукнкцией).
Селектор несуществующего нигде метода methUnexist1 располагается «рядом» с селекторами существующих методов. Возможно, из этого можно сделать вывод, что он также был добавлен в таблицу на этапе компиляции (т.к. @ selector — конструкция времени компиляции)?
Селектор несуществующего метода methUnexist2 уже отличается от остальных… такое впечатление, что он как раз был создан динамически и добавлен в таблицу уже в рантайме.
И наконец, селектор системного метода init вообще очень сильно отличается по значению. Интерпретировать это я уже не могу:)
опечатка, не успел исправить: вместо
SEL s6 = NSSelectorFromString(init);

следует читать
SEL s7 = @selector (init);
То, что вы вывели, всего навсего адреса селекторов в памяти. Они ничего принципиально не значат. В конечном итоге каждый селектор это просто С строка и не важно в какой области памяти процесса она лежит. Естественно строковые литералы будут лежать в другом месте совсем — селекторы таковыми и являются. Откройте любой objc бинарник в дизассемблере и среди строковых литералов найдете все методы и классы. Мы можем хоть в зашифрованном виде хранить эти строки и расшифровывать только при вызове метода через какой-нибудь NSInvocation — все будет работать как по маслу. Рантайм всего лишь ждет от нас строку и по ней найдет метод, если он есть у класса. Где бы она ни находилась — в литералах, куче, стеке. В этом весь смак objc runtime.
Да я не против:) Просто интересно было как оно устроено, в том числе с точки зрения компилятора. Концепция мне очень нравится, но интересно, можно ли ее еще улучшить, можно ли интегрировать с другими языками программирования (включая альтернативные реализации динамики — виртуальные функции C++, сигналы-слоты Qt, интерфейсы Go). И как это можно было бы сделать в своем собственном языке программирования.
Получается, что ObjC хранит все имена всех методов в виде строк; и в основной реализации адреса этих строк и являются по сути селекторами (хотя формально это необязательно — достаточно чтобы было соответствие между текстовым именем метода и некоторым числовым идентификатором). Адреса строк — это самая простая, тупая и быстрая реализация такого соответствия.
А дальше, насколько я понимаю, строятся какие-то словари, в чем-то похожие на таблицы виртуальных функций, где ключ — селектор, а значение — адрес метода? Надеюсь, автор нам расскажет об этом в следующей статье:)

Естественно там есть таблицы для быстрого поиска методов по селекторам. Никто при каждом вызове strcmp, скажем, не вызывает. В подробности того, какая именно там структура данных используется, я правда не вдавался. Но то, что вызов objc метода делается довольно быстро, это знаем точно www.mikeash.com/pyblog/performance-comparisons-of-common-operations-leopard-edition.html
Я так и не понял с чего вы взяли что селектор это обычная строка?
Спасибо за замечание. Добавил ответ на Ваш вопрос к статье.
Спасибо за обвноление, но есть подозрения что это не так на самом деле.
Если посмотреть в исходный код LLDB (начиная со строки ~1250), то видно что оно обрабатывает разные встроенные типы по-разному:

AddCXXSummary(objc_category_sp, lldb_private::formatters::ObjCSELSummaryProvider<false>, "SEL summary provider", ConstString("SEL"), objc_flags);
AddCXXSummary(objc_category_sp, lldb_private::formatters::ObjCSELSummaryProvider<false>, "SEL summary provider", ConstString("struct objc_selector"), objc_flags);
AddCXXSummary(objc_category_sp, lldb_private::formatters::ObjCSELSummaryProvider<false>, "SEL summary provider", ConstString("objc_selector"), objc_flags);
AddCXXSummary(objc_category_sp, lldb_private::formatters::ObjCSELSummaryProvider<true>, "SEL summary provider", ConstString("objc_selector *"), objc_flags);
AddCXXSummary(objc_category_sp, lldb_private::formatters::ObjCSELSummaryProvider<true>, "SEL summary provider", ConstString("SEL *"), objc_flags);


Я не копал глубже, но подозреваю что там все-таки не строка.
В подтверждение моего комментария предлагаю вам взглянуть на вывод для кастомной структуры и, допустим, NSRect:

P.S. почуму-то XVim показывает путь к файлу из другого проекта...
Вы абсолютно правы в своих рассуждениях, но ключевой момент тут в том, каким образом будет выглядеть такая структура в памяти. В Вашем случае структура

struct Dragon {
  char name[10];
}


будет представлена в памяти так же как и

char name[10];


То есть в исходном коде различия могут и быть, тут вы правы. Но предмет нашего обсуждения это именно время исполнения.
Кстати, я бы не исключал того, что char name[10] представляет собой только первую часть структуры, реализующей собой селектор. В таком случае, приводя селектор к char* — мы и будем видеть только лишь первую часть структуры.

svn.gna.org/svn/gnustep/libs/libobjc2/trunk/selector.h — вот тут например приводится альтернативный вариант, при котором над char * (которому, к слову не всегда соответствует первая часть структуры, экономии чего-то ради, будучи замененная на некоторый индекс) существует и еще одно поле types, которое хранит в себе, судя по всему, сигнатуру вызова.
Вообще да, к селекторам, по хорошему, так же привязан type encoding, который дает понять примерно какого типа аргументы. Но это информация уже конкретного класса, а не самого селектора. Селектор это имя метода, которое никак не привязано к конкретному классу и никак не описывает типы аргументов. Селектор «value» может возвращать какой угодно тип в зависимости от того, какому классу этот селектор дать. Он даже может означать одновременно метод класса и метод экземпляра. Лишний раз можно заглянуть в рантайм API developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html

Функции для работы с селекторами не позволяют получить никаких типов. Только имя, а это та самая С строка. Тип получается только для методов с помощью method_getTypeEncoding. Туда передается тип Method, который можно получить только от конкретного класса, передав ему тот самый универсальный селектор. И вот Method уже должен внутри себя содержать и селектор, и type encoding.

Так что представленный код выглядит довольно странно. Не вписывается в логику самого языка и его рантайма. И не вписывает в то, как это все хранится в бинарнике, где селекторы лежат отдельным списком С литералов, а отдельно лежат метаданные классов со ссылкой на эти литералы для каждого конкретного метода.
Я к тому, что полагаться на вывод дебаггера не совсем правильно.
Вам не хватает бэкграунда Smalltalk — именно оттуда растут ноги селекторов Objective-C. Селекторы были придуманы в своё время для экономии памяти и быстрого сравнения — все селекторы сидят в общем словаре, и если где-то объявляется уникальный, он туда добавляется, а потом, если где встречается ещё раз — уже добавляется только ссылка на него. Соответственно, сравнение селекторов для системы очень быстрая операция, проверяются только указатели.

Такие дела. А Смолток волшебен, у меня по нему старая добрая ностальния, хнык-хнык (
И к сожалению Apple решили весь этот динамизм закопать при переходе на Swift…
Думаю, Вы не правы. Apple говорит, что в основе Swift лежит любимый нами Objective-C Runtime. Это вдвойне радует, потому что наши знания не пропадут даром даже после перехода на Swift :).
Знания никогда не пропадут, а вот возможности запросто. Apple говорит, но там есть хитрость. Насколько помню, если swift класс используется в objc коде, то там действительно все как в старом добром objc runtime. Но если swift класс не используется в objc, то он превращается, по сути, в подобие C++ классов с жестко заданной таблицей виртуальных функций, которая находится после всех метаданных. Никаких селекторов, прямые вызовы методов. Swift на самом деле сильно другой внутри. Есть вот такая лекция небольшая на эту тему www.youtube.com/watch?v=Ii-02vhsdVk
Более того, из свифта принципиально нельзя дернуть retain/release или performSelector. Очень больно, учитывая что компилятор over-release местами себе устраивает.
Не совсем так. В Свифте объявление класса можно написать двумя способами:

class Foo : NSObject {

либо

class Foo {

В первом случае Foo будет полноценным NSObject'ом, с которым можно пользоваться Objective-C рантаймом как нам хочется. Во втором случае это чисто свифтовый объект и вызов методов будет идти через свифтовый рантайм.
Вы еще не упомянули ключевое слово dynamic для свойств.
UFO just landed and posted this here
«Сегодня он всё так же кажется мне уродливым. Но однажды окунувшись в глубины Objective-C Runtime я влюбился в него.»
В очередной раз убеждаюсь в прописной истине: Любовь зла, полюбишь и козла.
Sign up to leave a comment.

Articles