Вызов метода перехвата в Objective-C

Можно ли перехватить вызов метода в Objective-C? Как?

Edit: Ответ Марка Пауэлла дал мне частичное решение, метод -forwardInvocation. Но в документации указывается, что -forwardInvocation вызывается только тогда, когда объект отправляется сообщение, для которого у него нет соответствующего метода. Я бы хотел, чтобы метод вызывался при любых обстоятельствах, даже если у получателя есть этот селектор.

Ответ 1

Вы делаете это, выполняя вызов метода. Предполагая, что вы хотите захватить все релизы в NSTableView:

static IMP gOriginalRelease = nil;
static void newNSTableViewRelease(id self, SEL releaseSelector, ...) {
   NSLog(@"Release called on an NSTableView");
   gOriginalRelease(self, releaseSelector);
}


  //Then somewhere do this:
  gOriginalRelease = class_replaceMethod([NSTableView class], @selector(release), newNSTableViewRelease, "[email protected]:");

Вы можете получить более подробную информацию в режиме выполнения Objective C documentation.

Ответ 2

Вызов метода перехвата в Objective-C (при условии, что это Objective-C, а не вызов C) выполняется с помощью метода, называемого методом swizzling.

Вы можете найти введение о том, как реализовать этот здесь. Например, как метод swizzling реализуется в реальном проекте, проверьте OCMock (Изоляционная основа для Objective-C).

Ответ 3

Отправка сообщения в Objective-C преобразуется в вызов функции objc_msgSend(receiver, selector, arguments) или одного из ее вариантов objc_msgSendSuper, objc_msgSend_stret, objc_msgSendSuper_stret.

Если бы можно было изменить реализацию этих функций, мы могли бы перехватить любое сообщение. К сожалению, objc_msgSend является частью среды выполнения Objective-C и не может быть переопределен.

По googling я нашел статью в Google Книгах: Отражающая архитектура для приложений управления технологическими процессами Шарлотты Пий Лунау. В документе вводится хак, перенаправляя указатель класса объекта isa к экземпляру пользовательского класса MetaObject. Таким образом, сообщения, предназначенные для измененного объекта, отправляются экземпляру MetaObject. Поскольку класс MetaObject не имеет собственных методов, он может затем ответить на обратный вызов, перенаправив сообщение на измененный объект.

В документе нет интересных битов исходного кода, и я понятия не имею, будет ли такой подход иметь побочные эффекты в Cocoa. Но может быть интересно попробовать.

Ответ 4

Если вы хотите регистрировать отправку сообщений с вашего кода приложения, то -forwardingTargetForSelector: tip является частью решения.
Оберните свой объект:

@interface Interceptor : NSObject
@property (nonatomic, retain) id interceptedTarget;
@end

@implementation Interceptor
@synthesize interceptedTarget=_interceptedTarget;

- (void)dealloc {
    [_interceptedTarget release];
    [super dealloc];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"Intercepting %@", NSStringFromSelector(aSelector));
    return self.interceptedTarget;
} 

@end

Теперь сделаем что-то вроде этого:

Interceptor *i = [[[Interceptor alloc] init] autorelease];
NSFetchedResultsController *controller = [self setupFetchedResultsController];
i.interceptedTarget = controller;
controller = (NSFetchedResultsController *)i;

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

Ответ 5

Если вы хотите только регистрировать сообщения, вызываемые извне (обычно вызываемые из делегатов, чтобы видеть, какие сообщения, когда и т.д.), вы можете переопределить responsesToSelector следующим образом:

- (BOOL)respondsToSelector:(SEL)aSelector {
NSLog(@"respondsToSelector called for '%@'", NSStringFromSelector(aSelector));

// look up, if a method is implemented
if([[self class] instancesRespondToSelector:aSelector]) return YES;

return NO;

}

Ответ 6

Создайте подкласс NSProxy и внесите -forwardInvocation: и -methodSignatureForSelector: (или -forwardingTargetForSelector:, если вы просто направляете его на второй объект вместо того, чтобы возиться с методом самостоятельно).

NSProxy - класс, предназначенный для реализации -forwardInvocation: on. У него есть несколько методов, но в основном вы не хотите, чтобы их поймали. Например, улавливание методов подсчета ссылок предотвратит освобождение прокси-сервера, за исключением сбора мусора. Но если на NSProxy есть специальные методы, которые вам абсолютно необходимо перенаправить, вы можете переопределить этот метод и вызвать -forwardInvocation: вручную. Обратите внимание, что просто потому, что метод указан в документации NSProxy не означает, что NSProxy реализует его, просто потому, что ожидается, что все проксированные объекты имеют его.

Если это не сработает для вас, предоставьте дополнительную информацию о вашей ситуации.

Ответ 7

Возможно, вам нужен метод NSObject -forwardInvocation. Это позволяет вам захватить сообщение, перенаправить его и затем повторно отправить.

Ответ 8

Вы можете совершить вызов метода одним из своих, что делает все, что вы хотите сделать при "перехвате", и обращается к исходной реализации. Swizzling выполняется с помощью class_replaceMethod().

Ответ 9

Вызов метода, нет. Сообщение отправлено, да, но вам придется быть более подробным, если вы хотите получить хороший ответ о том, как это сделать.

Ответ 10

Чтобы что-то сделать при вызове метода, вы можете попробовать подход, основанный на событиях. Поэтому, когда метод вызывается, он транслирует событие, которое воспринимается всеми слушателями. Я не очень хорош в объективе C, но я просто понял что-то подобное, используя NSNotificationCenter в Cocoa.

Но если "перехватить" означает "остановить", то, возможно, вам понадобится больше логики, чтобы решить, должен ли метод вообще вызываться.