Хорошо, я, наконец, добрался до WoolDelegate на GitHub. Теперь мне понадобится только месяц, чтобы написать правильный README (хотя я думаю, что это хороший старт).
Сам класс делегата довольно прост. Он просто поддерживает сопоставление словаря SEL
s для блокировки. Когда экземпляр recieves сообщение, к которому он не отвечает, он заканчивает в forwardInvocation:
и смотрит в словарь для выбора:
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = [anInvocation selector];
GenericBlock handler = [self handlerForSelector:sel];
Если он найден, указатель вызова функции Блока вытянут и прошел вдоль до сочных бит:
IMP handlerIMP = BlockIMP(handler);
[anInvocation Wool_invokeUsingIMP:handlerIMP];
}
(BlockIMP()
функции, наряду с другим блоком-зондирующего кодом, благодаря Mike Ash на самом деле, многое из этого проекта строится на материале, который я узнал от его пятницы Q & в.. Если вы не читали эти эссе, вам не хватает.)
Следует отметить, что это происходит через механизм обработки полного метода каждый раз, когда отправляется конкретное сообщение; там есть скорость. Альтернативой является путь, которым занимались Эрик Х. и EMKPantry, который создает новый клас для каждого объекта-делегата, который вам нужен, и используя class_addMethod()
.Поскольку каждый экземпляр WoolDelegate
имеет свой собственный словарь обработчиков, нам не нужно это делать, но, с другой стороны, нет возможности «кэшировать» поиск или вызов. Метод может быть добавлен только к классу , а не к экземпляру.
Я сделал это по двум причинам: это было упражнение, чтобы посмотреть, смогу ли я разработать часть, которая будет следующей: раздача с NSInvocation
для блокировки вызова - и создание нового класса для каждого необходимого экземпляр просто казался мне неэлегантным. Является ли это менее элегантным, чем мое решение, я оставлю все суждения каждого читателя.
Перемещение, мясо этой процедуры фактически находится в NSInvocation
category, что находится в проекте. Это использует libffi для вызова функции, которая неизвестна до выполнения - вызов Блока - с аргументами, которые также неизвестны до времени выполнения (которые доступны через NSInvocation
). Как правило, это невозможно, по той же причине, что нельзя передать va_list
: компилятор должен знать, сколько аргументов есть и насколько они велики. libffi содержит ассемблер для каждой платформы, который знает/основан на этих платформах calling conventions.
Здесь три шага: libffi нуждается в списке типов аргументов вызываемой функции; ему нужны сами значения аргументов в конкретный формат; то функция (указатель вызова блока) должна быть вызвана через libffi, а возвращаемое значение возвращается обратно в NSInvocation
.
Реальная работа для первой части обрабатывается в основном функцией, которая снова написана Майком Эшем, вызванным от Wool_buildFFIArgTypeList
. libffi имеет внутренний struct
s, который используется для описания типов аргументов функции. При подготовке вызова функции библиотека нуждается в списке указателей на эти структуры. NSMethodSignature
для NSInvocation
допускает доступ к строке кодирования каждого аргумента; перевод оттуда к правильному ffi_type
обрабатывается набором if
/else
поисков:
arg_types[i] = libffi_type_for_objc_encoding([sig getArgumentTypeAtIndex:actual_arg_idx]);
...
if(str[0] == @encode(type)[0]) \
{ \
if(sizeof(type) == 1) \
return &ffi_type_sint8; \
else if(sizeof(type) == 2) \
return &ffi_type_sint16; \
Далее libffi хочет указатели на аргумент сами значения. Это делается в Wool_buildArgValList
: получить размер каждого аргумента, опять-таки из NSMethodSignature
и выделить кусок памяти, что размер, а затем возвращает список:
NSUInteger arg_size;
NSGetSizeAndAlignment([sig getArgumentTypeAtIndex:actual_arg_idx],
&arg_size,
NULL);
/* Get a piece of memory that size and put its address in the list. */
arg_list[i] = [self Wool_allocate:arg_size];
/* Put the value into the allocated spot. */
[self getArgument:arg_list[i] atIndex:actual_arg_idx];
(В сторону: есть несколько заметок в коде о пропуская через SEL
, который является (скрытым) вторым переданным аргументом для любого вызова метода. Указатель вызова Блока не имеет слота для хранения SEL
, он просто сам по себе является первым аргументом, а остальные являются «нормальными» «Поскольку блок, как написано в клиентском коде, никогда не мог получить доступ к этому аргументу в любом случае (он не существует в то время), я решил проигнорировать его.)
libffi теперь нужно сделать «prep»; до тех пор, как успешно (и пространство для возвращаемого значения может быть выделено), указатель вызова функции теперь может быть «называется», а возвращаемое значение может быть установлено:
ffi_call(&inv_cif, (genericfunc)theIMP, ret_val, arg_vals);
if(ret_val){
[self setReturnValue:ret_val];
free(ret_val);
}
Там в некоторых демонстрациях функциональных возможностей в main.m в проекте.
Наконец, что касается вашего вопроса о том, «должно ли это быть сделано?», Я думаю, что ответ «да, пока он делает вас более продуктивным».WoolDelegate
является полностью общим, и экземпляр может действовать как любой полностью выписанный класс. Однако мое намерение заключалось в том, чтобы сделать простых, единовременных делегатов - для этого нужны только один или два метода, и им не нужно проживать мимо их делегатов - меньше работы, чем писать совершенно новый класс, и более разборчивым/поддерживаемый, чем приклеить некоторые методы делегата в контроллер просмотра, потому что это самое простое место для их размещения. Воспользовавшись временем выполнения и динамизмом языка, как это, мы надеемся, может повысить читаемость вашего кода, точно так же, например, Block-based NSNotification
handlers.
Другими словами, я хотел бы реализовать анонимные классы Java с блоками. – hpique
Это возможно, но в общем случае требуется перейти от 'NSInvocation' к блоку через libffi. У меня есть класс, плавающий вокруг на моем жестком диске, который делает именно то, что находится в вашем фрагменте кода, но я уезжаю из города на неделю и не смогу опубликовать много кода, пока не вернусь. –
@JoshCaswell Neat. Не торопись. :) – hpique