AOP в Objective-C: вводить контекстно-зависимый код в каждый метод, поддерживая DRY - PullRequest
8 голосов
/ 14 февраля 2012

ОБНОВЛЕНИЕ:

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

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

Несколько раз (но, конечно, не очень часто) я сталкивался с ситуацией, когда мой код был бы немного СУХИМ, если бы я мог вызвать контекстно-зависимый фрагмент кода для каждого метода в классе. Использование среды выполнения 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 будет содержать больше кода, и в классе будет больше методов.

П.С., я довольно одержим СУХОЙ. Наряду с ясностью прозы и производительности, это ключевой компонент того, как я оцениваю качество своего кода в долгосрочной перспективе. Для каждого нового способа, которым я могу избежать повторения, преимущество является экспоненциальным, потому что я разрываю как можно больше кода в повторно используемых классах, которые совместно используются во многих проектах.

1 Ответ

5 голосов
/ 15 февраля 2012

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

Работающий образецможно найти в этой сущности .

...