Заставить NSInvocation вызвать определенный IMP - PullRequest
10 голосов
/ 19 февраля 2011

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

Я подумал о том, чтобы использовать механизм -forwardInvocation: для захвата сообщения (легкого и уже работающего), а затем изменить IMP, чтобы он перешел к методу, который не является ни реализацией super, ни реализацией самого дальнего потомка. (Жесткий)

Единственное, что я нашел удаленно близко, это AspectObjectiveC , но для этого требуется libffi, что делает его несовместимым с iOS. В идеале я хотел бы, чтобы это было кроссплатформенным.

Есть идеи?

отказ от ответственности: я просто экспериментирую


Испытание идеи @bbum о батуте

Так что я думаю, что у меня все настроено; У меня есть следующий батут, который правильно добавляется через class_addMethod(), и он вводится:

id dd_trampolineFunction(id self, SEL _cmd, ...) {
    IMP imp = [self retrieveTheProperIMP];
    self = [self retrieveTheProperSelfObject];
    asm(
        "jmp %0\n"
        :
        : "r" (imp)
        );
    return nil; //to shut up the compiler
}

Я проверил, что и собственное Я, и правильный ИМП являются правильными вещами до JMP, и параметр _cmd также входит правильно. (другими словами, я правильно добавил этот метод).

Однако что-то происходит. Я иногда ловлю себя на том, что прыгаю к методу (обычно не правильному) с nil self и _cmd. В других случаях я просто потерплю неудачу с EXC_BAD_ACCESS. Идеи? (я давно ничего не делал в сборке ...) Я тестирую это на x86_64.

Ответы [ 7 ]

4 голосов
/ 19 февраля 2011

NSInvocation - это просто объектное представление отправляемого сообщения.Таким образом, он не может вызвать конкретный IMP больше, чем обычная отправка сообщения.Чтобы вызов вызывал конкретную IMP, вам нужно либо написать собственный класс NSInvocation, который проходит через процедуру вызова IMP, либо вам нужно написать батут, который реализует поведение, а затем создать вызов, который представляетсообщение батуту (то есть вы, по сути, не будете использовать NSInvocation для чего-либо).

3 голосов
/ 06 февраля 2013

Добавлено задолго до факта, для справки:

Вы можете сделать это с помощью частного API.Поместите эту категорию где-нибудь удобно:

@interface NSInvocation (naughty)
-(void)invokeUsingIMP:(IMP)imp;
@end

и вуаля, она делает именно то, что вы ожидаете.Я выкопал этот драгоценный камень из одного из старых постов Майка Эша.

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

3 голосов
/ 19 февраля 2011

Учитывая, что у вас уже есть IMP, вам просто нужен способ сделать очень грубую переадресацию вызова метода к указанному IMP.И учитывая, что вы готовы использовать решение, подобное NSInvocation, вы также можете создать аналогичный прокси-класс.

Если бы я столкнулся с этим, я бы создал простой прокси-класс, который содержал бы IMP для вызоваи целевой объект (вам нужно установить параметр self).Затем я напишу функцию-батут в ассемблере, которая принимает первый аргумент, предполагает, что это экземпляр прокси-класса, захватывает self, вставляет его в регистр, содержащий аргумент 0, захватывает IMP и * JMP в него какхвостовой вызов.

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

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

3 голосов
/ 19 февраля 2011

Неопробованная идея:

Не могли бы вы использовать object_setClass() для принудительного выбора IMP, который вы хотите? Это ...

- (void)forwardInvocation:(NSInvocation *)invocation {
    id target = [invocation target];
    Class targetClass = classWithTheImpIWant();
    Class originalClass = objc_setClass(target, targetClass);
    [invocation invoke];
    objc_setClass(target, originalClass);
}
2 голосов
/ 19 февраля 2011

Я думаю, что ваш лучший выбор - использовать libffi.Вы видели порт для iOS на https://github.com/landonf/libffi-ios? Я не пробовал порт, но я успешно вызвал IMP с произвольными аргументами на Mac.

Посмотрите на JSCocoa https://github.com/parmanoir/jscocoa содержит код, помогающий вам подготовить структуру ffi_cif из Method, а также версию libffi, которая должна компилироваться на iOS.(Тоже не проверял)

0 голосов
/ 08 октября 2014

вы можете добавить IMP к классу, используя class_addMethod под новым селектором, и вызвать этот селектор.

хотя временный метод не может быть удален.

0 голосов
/ 31 марта 2014

Вы, вероятно, должны взглянуть на то, как мы извергаем реализацию определенного метода на экземпляре объекта в https://github.com/tuenti/TMInstanceMethodSwizzler

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

Вы также можете использовать закрытый метод invokeWithImp:, но это не рекомендуется, если вы намереваетесь отправить приложение в магазин.

...