Bezdepbonusu7Программирование: статьи и ответы

Вопрос

АОП в Objective-C: внедрение контекстно-зависимого кода в каждый метод при сохранении DRY

Автор вопроса: james_womackОтветов: 1

ОБНОВЛЕНИЕ:

Обратившись к Джорджу с некоторыми ключевыми предложениями, я придумал два разных способа добиться именно того, чего я хочу, в CodeRunner, и разместил их на основном сайте Github: Objective-C AOP gist

Код грубый, потому что это новая концепция, а я закончил только в 1:30. Это определенно работает и имеет некоторые тонкости, такие как автоматическое добавление всех методов, которые не являются инициализаторами, геттерами или сеттерами. [КОНЕЦ ОБНОВЛЕНИЯ]

Несколько раз (но, конечно, не очень часто) я сталкивался с ситуацией, когда мой код был бы немного DRYer, если бы я мог вызывать контекстно-зависимый фрагмент кода для каждого метода в классе. Использование среды выполнения Objective-C совершенно нормально, я бы также принял решения C или C++.

Вместо:

- (void)methodName1
{
   self->selector = _cmd;
   NSLog(@"This method is named: %@",_cmd);
   //more code
}

- (void)methodName2
{
   self->selector = _cmd;
   NSLog(@"This method is named: %@",_cmd);
   //more code
}

Сделайте что-то вроде этого, с тем же результатом:

+ (void)AOPMethod
{
   self->selector = _cmd;
   NSLog(@"This method is named: %@",_cmd);
}

- (void)methodName1
{
   //more code
}

- (void)methodName2
{
   //more code
}

В реальном приложении AOPMethod будет содержать больше кода и в классе будет больше методов.

P.S. Я довольно одержим DRY. Наряду с ясностью изложения и производительностью это ключевой компонент того, как я оцениваю качество своего кода в долгосрочной перспективе. Для каждого нового способа, которым я могу избежать повторения, выгода экспоненциальна, потому что я прерываю как можно больше кода в повторно используемых классах, которые используются во многих проектах.

  • Вы просто ищете перехваты входа/выхода? Georg Fritzsche 14.02.2012
  • @GeorgFritzsche Спасибо за вопрос. Если перехват метода не зависит от платформы и позволит использовать контекстно-зависимые данные уровня метода, такие как _cmd, в каждом методе класса - и без дублирования кода - тогда да. Другими словами, если вы можете поделиться методом на одном из упомянутых базовых языков (а не на фреймворках, доступных не на всех платформах), который позволит некоторой версии последнего примера иметь тот же результат, что и первый, то это ответ. Еще раз спасибо. james_womack 14.02.2012
  • Для вашего конкретного варианта использования (все методы с одинаковой сигнатурой) этот подход можно расширить, чтобы исправить все подходящие методы в списке методов. На данный момент я не могу придумать элегантного более общего решения, только неэффективные. Georg Fritzsche 14.02.2012
  • @GeorgFritzsche Я думаю, что какая-то версия предложенной вами техники по предоставленной вами ссылке может сработать. Однако я провел некоторый тест и не нашел способа сделать один или два вызова, которые настраивают это для каждого метода. В конце концов, я думаю, что использование NSProxy может быть чище. james_womack 15.02.2012
  • То, что я связал, всегда требует наличия метода перехватчика (то есть того, который будет вызывать обработчики до и после) с сигнатурой метода нужных вам методов. Единственное более общее решение, которое я могу придумать, - это обработать его с помощью NSInvocations, но для вызова forwardInvocation: вам нужно удалить исходные методы (больше невозможно в ObjC 2). Я думаю, что NSProxy стэндины - это лучшая вещь, не прибегающая к хакам. Georg Fritzsche 15.02.2012
  • @GeorgFritzsche Благодаря вашему руководству и моей тяжелой работе я разработал рабочие решения, используя либо NSProxy + свойства блоков ввода/вывода + прокручивание, либо кэширование IMP во время выполнения + логический оператор (для включения или выключения входа/выхода) + шипение. Я инкапсулировал оба способа достижения АОП в подклассе NSProxy для максимальной гибкости. Мой класс интеллектуально предотвращает дублирование методов инициализации, геттеров и сеттеров с помощью префикса (префикс является частью того, как работает кэширование/пересылка методов IMP). Не стесняйтесь перемещать. james_womack 15.02.2012

cc++objective-cdryaop

Ответы

Ответ 1 · рейтинг 5 · Georg Fritzsche

Для конкретного варианта использования в вопросе можно предоставить обработчик, который заменяет исходные функции реализации и вызывает обработчики до/после, а также исходные функции, используя что-то вроде этот подход. В целом, однако, исправление реализации метода не будет работать, поскольку для каждой перехваченной сигнатуры метода необходимо будет предоставить метод обработчика/перехвата.

Что будет работать в более общем плане (т. е. для всего, кроме функций с переменными аргументами), будет обрабатывать -forwardInvocation:. Однако проблема здесь в том, что нам нужно было бы вызвать этот метод в первую очередь. Поскольку мы не можем удалить методы в ObjC2, это невозможно сделать на месте.

Что можно сделать, так это использовать прокси, которые реализуют forwardInvocation: и вызывают наши обработчики до/после.

@interface AspectProxy : NSProxy {
    id target_;
}
- (id)initWithTarget:(id)target;
@end

@implementation AspectProxy
- (id)initWithTarget:(id)target {
    target_ = [target retain];
    return self;
}
- (void)dealloc {
    [target_ release];
    [super dealloc];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [target_ methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)inv {
    SEL sel = [inv selector];
    NSLog(@"forwardInvocation for: %@", NSStringFromSelector(sel));
    if (sel == @selector(aspectBefore:) || sel == @selector(aspectAfter:)) {
        return;
    }
    if ([target_ respondsToSelector:@selector(aspectBefore:)]) {
        [target_ performSelector:@selector(aspectBefore:) withObject:inv];
    }
    [inv invokeWithTarget:target_];
    if ([target_ respondsToSelector:@selector(aspectAfter:)]) {
        [target_ performSelector:@selector(aspectAfter:) withObject:inv];
    }
}
@end

Поскольку нам не нужно возвращать фактический экземпляр из метода init, это можно сделать даже прозрачно:

@interface Test : NSObject
- (void)someFunction;
@end

@implementation Test
- (id)init {
    if (self = [super init]) {
        return [[AspectProxy alloc] initWithTarget:[self autorelease]];
    }
    return self;
}
- (void)aspectBefore:(NSInvocation *)inv {
    NSLog(@"before %@", NSStringFromSelector([inv selector]));
}
- (void)aspectAfter:(NSInvocation *)inv {
    NSLog(@"after %@", NSStringFromSelector([inv selector]));
}
- (void)someFunction {
    NSLog(@"some function called");
}
@end

Теперь следующий код:

Test *x = [[[Test alloc] init] autorelease];
[x someFunction];

... напечатает:

forwardInvocation для: someFunction
перед someFunction
некоторой функцией, вызываемой
после someFunction

Запускаемый пример можно найти в этой сути.

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы