ОБНОВЛЕНИЕ:
Обратившись к Джорджу с некоторыми ключевыми предложениями, я придумал два разных способа добиться именно того, чего я хочу, в 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. Наряду с ясностью изложения и производительностью это ключевой компонент того, как я оцениваю качество своего кода в долгосрочной перспективе. Для каждого нового способа, которым я могу избежать повторения, выгода экспоненциальна, потому что я прерываю как можно больше кода в повторно используемых классах, которые используются во многих проектах.
Ответ 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
Запускаемый пример можно найти в этой сути.