Сценарии Какао: перехватывать любые вызовы первого сообщения объекта
Я пишу свою собственную не-ObjC-среду на основе сценариев Какао (подумайте о написании сценариев Mac-приложения на C, C++ или Java или, в моем случае, Xojo).
Мне нравится иметь возможность перехватывать любой вызов метода Object-first вместо того, чтобы добавлять фактический метод в класс ObjC (я не могу, потому что фреймворк не будет знать, какое сообщение код приложения может обработать заранее, поэтому Вместо этого мне придется получать и передавать любые командные сообщения, как только они поступят из механизма сценариев).
Например, любые методы получения и установки свойств могут быть перехвачены посредством реализации
-valueForUndefinedKey:
-setValue:forUndefinedKey:
а также все методы NSScriptKeyValueCoding
протокол.
Я ищу аналогичный способ перехвата NSCommandScript
сообщения, отправленные методу, указанному в этих элементах sdef:
<responds-to command="reload">
<cocoa method="reloadList:"/>
</responds-to>
Таким образом, вместо реализации reloadList:
добавляя его в методы класса, мне интересно, есть ли общий способ перехвата всех таких вызовов.
Я обнаружил, что метод класса
+ (BOOL)resolveInstanceMethod:(SEL)sel
вызывается с просьбой reloadList:
, Но тот же метод вызывается и для многих других целей, и поэтому я не буду слепо перехватывать каждый такой вызов, потому что это вызовет довольно серьезное снижение производительности, если я перенаправлю их все в функцию Java, которая сообщает мне, хочет ли она справиться с этим, например.
Я надеюсь, что есть кое-что, что позволяет мне сказать, что этот селектор связан с NSScriptCommand, прежде чем пересылать его дальше.
2 ответа
После установки точки останова в назначенном методе обработки команд я увидел следующую трассировку стека:
#0 0x00000001000197db in -[SKTRectangle rotate:]
#1 0x00007fff8ee0b7bc in __invoking___ ()
#2 0x00007fff8ee0b612 in -[NSInvocation invoke] ()
#3 0x00007fff8eeab5c6 in -[NSInvocation invokeWithTarget:] ()
#4 0x00007fff8b82cbde in -[NSScriptCommand _sendToRemainingReceivers] ()
#5 0x00007fff8b82cf39 in -[NSScriptCommand executeCommand] ()
Это показывает, что NSScriptCommand, по-видимому, не использует какой-либо настраиваемый специальный механизм пересылки, а использует NSInvocation для вызова метода.
Такие вызовы могут быть перехвачены следующим образом:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
// Look for signatures with exactly one ":"
if ([[NSStringFromSelector(aSelector) componentsSeparatedByString:@":"] count] == 2) {
return [NSMethodSignature signatureWithObjCTypes:"@:@@"];
} else {
return [super methodSignatureForSelector:aSelector];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
id arg; // The first and only argument is expected to be of type NSScriptCommand*
[anInvocation getArgument:&arg atIndex:2];
if ([arg isKindOfClass:[NSScriptCommand class]]) {
NSLog(@"executing the command...");
// when finished, set the return value (which shall be an NSObject)
id result = nil;
[anInvocation setReturnValue:&result];
} else {
// oops - we cannot handle this
[super forwardInvocation:anInvocation];
}
}
Эта форма перехвата работает лучше, чем использование resolveInstanceMethod:
потому что он вызывается не так часто, а только для определенных целей, таких как выполнение NSScriptCommand.
Проблема с этим, однако, заключается в том, что если другой код также использует NSInvocation для выполнения вызовов в тот же класс для других целей, и если эти вызовы используют совпадающую сигнатуру селектора, приведенный выше код будет перехватывать эти вызовы и затем не обрабатывать их, возможно приводя к неожиданному поведению.
Поскольку известно, что классы используются только механизмом сценариев и не имеют другого поведения (т. Е. Являются непосредственными подклассами NSObject), нет никаких причин для этого. Таким образом, в моем особом случае, когда классы действуют только как прокси в другой среде, это может быть жизнеспособным решением.
Если это не приложение на основе Какао, то вам, вероятно, лучше забыть об использовании сценариев Какао, так как оно тесно связано с остальной частью архитектуры Какао, устанавливайте свои собственные обработчики AE напрямую, используя NSAppleEventManager
и напишите свой собственный клей View-Controller между ними и тем, во что вы в конечном итоге внедрите свою модель. См. также: Scriptable (AppleScript) в приложении Mac Carbon
ETA: Если подумать, возможно, вы захотите покопаться в Интернете и посмотреть, сможете ли вы углубиться в какие-либо старые C++ AEOM-фреймворки, так как ISTR были один или два назад в дни до OS X. Может потребовать некоторого обновления, и может быть, а может и не быть хорошим (но тогда CS тоже довольно дурацкий), но это было бы гораздо проще, чем начинать с нуля, как разработка и реализация хорошего, надежного, идиоматического (или даже упрощенного) Среда AEOM - это гигантская PITA, даже когда вы знаете, что делаете (и вряд ли кто-то знает).