Звонить супер без ключевого слова супер

Я хочу реализовать функцию «Исправить и продолжить», которая была в Xcode 3.

КОНТЕКСТ:

Основная идея такова: когда мне нужно "быстро что-то исправить", я не перекомпилирую проект. Я компилирую небольшой Attacker class с "обновленной" реализацией метода, загружаю его в память и заменяю метод VictimClass, который имеет incorrect реализацию во время выполнения. Я думаю, что этот метод будет работать быстрее, чем полная перекомпиляция проекта.

Когда я закончу с исправлениями, я просто скопирую исходный код метода Attacker class в Victim class.

ПРОБЛЕМА

На данный момент я не знаю, как правильно вызывать [super ...] в классе Attacker.

Например, у меня есть VictimClass

@interface VictimClass : UIView @end
@implementation VictimClass
- (void)drawRect:(CGRect)rect {
  [super drawRect:rect];
}
@end


@interface AttackerClass : NSObject @end
@implementation AttackerClass 
- (void)drawRect:(CGRect)rect {
  [super drawRect:rect];
  [self setupPrettyBackground];
}
@end
....

// EXCHANGE IMPLEMENTATIONS
Method m = class_getInstanceMethod([AttackerClass class], @selector(drawRect:));
const char * types = method_getTypeEncoding(m);
IMP attackerImp = method_getImplementation(m);

class_replaceMethod([VictimClass class], @selector(drawRect:), attackerImp, types);


// Invoking drawRect on Victim
VictimClass * view = /* */;
[view setNeedsDisplay];

В этот момент, когда будет вызываться метод drawRect:, это приведет к исключению, так как drawRect: будет вызываться в классе NSObject, а не в классе UIView.

Итак, мой вопрос, как правильно вызвать [super drawRect:] в AttackerClass, чтобы иметь возможность корректно поменять реализацию во время выполнения?

Основная идея состоит в том, чтобы обеспечить способ корректной замены любого метода в классе Victim методом класса Attacker. В общем, вы не знаете, суперкласс Victim class.

ОБНОВЛЕНИЕ: добавлен заменяющий код реализации.


person tt.Kilew    schedule 26.04.2012    source источник
comment
до сих пор не понимаю, что такое метод замены во время выполнения.   -  person Amitg2k12    schedule 26.04.2012
comment
Почему вы меняете суперкласс AttackerClass с NSObject на UIView?   -  person Jonathan Naguin    schedule 26.04.2012
comment
он не... у него просто 2 отдельных класса   -  person meronix    schedule 26.04.2012
comment
мне нравится, как вы используете класс атакующего и класс жертвы в своем вопросе.. очень хорошая аналогия для упрощения относительно сложного сценария   -  person abbood    schedule 09.10.2014
comment
Вы знаете, как сделать вышеперечисленное, не попадая в рекурсивный вызов super? см. обновление моего вопроса здесь   -  person abbood    schedule 09.10.2014


Ответы (4)


Тебе придется

  1. получить класс получателей (например, с object_getClass(rcv))
  2. затем получите его суперкласс (с class_getSuperclass(class))
  3. затем получите его реализацию (с class_getMethodImplementation(superclass, sel))
  4. затем вызовите чертенка.

Готово

Остановитесь на любом шаге, если вы получили nil или NULL.

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

[Обновление]

Пояснение для будущих читателей:

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

person Tilo Prütz    schedule 26.04.2012
comment
Хм... Я попробую это сделать. И сказать вам результат. возможно это правильный ответ. - person tt.Kilew; 26.04.2012
comment
Спасибо, чувак, используя это поведение, ты действительно можешь подражать супервызову. - person tt.Kilew; 27.04.2012
comment

Предполагая, что изменения во время выполнения, которые вы вносите, связаны с изменением суперкласса, вам нужно будет сделать что-то вроде этого:

@implementation AttackerClass 
-(void) drawRect:(CGRect)rect
{
    if( [super respondsToSelector:@selector(drawRect:)] )
    {
        [super drawRect:rect];
    }
    [self setupPrettyBackground];
}
@end

Это проверит, «знает ли» суперкласс drawRect:, и не вызывает ли его в случае, если super не имеет селектора drawRect:.

Следовательно, когда суперкласс равен NSObject, сообщение drawRect: не будет отправлено. Когда вы измените его на UIView во время выполнения (какой бы ни была ваша причина для этого), сообщение можно безопасно отправить.

person inspector-g    schedule 26.04.2012
comment
вы правы... но это правда, что когда вы пишете свой код, вы уже знаете, что ваш суперкласс не имеет этого метода... не нужно его вызывать и не нужно проверять... - person meronix; 26.04.2012
comment
@meronix согласился, но намекнул, что меняет суперкласс во время выполнения и не хочет отправлять сообщение drawRect:, пока суперкласс не будет установлен. - person inspector-g; 26.04.2012
comment
Мне все еще нужно вызвать эту супер... реализацию метода. Ваш способ - просто пропустить вызов super. Но мне нужно вызвать его так же, как это было до замены метода. - person tt.Kilew; 26.04.2012
comment
Это не имеет никакого смысла. Вы не можете отправить сообщение суперклассу, который не отвечает на указанное сообщение, и ожидать, что оно сработает. Единственный способ избежать исключения во время выполнения — не отправлять сообщение суперклассу в первую очередь. Если вы заменяете метод вместо замены суперкласса, это по-прежнему безопасно и правильно. - person inspector-g; 26.04.2012

Один из подходов — использовать objc_msgSendSuper. Ваш метод -[AttackerClass drawRect:] будет иметь следующую реализацию:

- (void)drawRect:(CGRect)rect {
  struct objc_super superTarget;
  superTarget.receiver = self;
  superTarget.class = object_getClass(self);
  objc_msgSendSuper(&superTarget, @selector(drawRect:), rect);
  [self setupPrettyBackground];
}
person Nate Chandler    schedule 10.05.2012
comment
Кстати, я использовал ваш ответ здесь.. работал как шарм! :) - person abbood; 09.10.2014

но зачем вам вызывать метод draw rect для суперкласса NSObject, если у NSObject нет этого метода? просто не делайте этого... назовите это просто в VictimClass drawrect

person meronix    schedule 26.04.2012