iOS - использовать макросы для пересылки множества сообщений?
ForwardInvocation существует, но он медленный и имеет раздражающую проблему предупреждений компилятора. Так что это заставило меня задуматься - есть ли способ использовать макросы для быстрой реализации набора методов получения, которые получают рассматриваемое свойство от другого объекта?
Например, если у меня есть объект Car, он может реализовать следующее:
Car.h:
@class SparkPlug;
@class Engine;
. . .
-(int) nPistons;
-(float) horsepower;
-(SparkPlug*) sparkPlug;
Car.m:
. . .
-(int) nPistons {
return self.engine.nPistons;
}
-(float) horsepower {
return self.engine.horsepower;
}
-(SparkPlug*) sparkPlug {
return self.engine.sparkPlug;
}
Вопрос - можно ли было бы настроить некоторые макросы так, чтобы, сделав одно изменение где-нибудь, я мог добавить еще один такой метод как в заголовочный файл, так и в файлы реализации?
например, MagicForwardingMacro (nPistons, int, engine);
В идеале, таким образом, чтобы макросы можно было многократно использовать, если бы позже я захотел позже использовать аналогичную стратегию, чтобы получить свойства Person из firstCame и сертификата firstName, lastName, placeOfBirth и dateOfBirth.
3 ответа
Самый простой способ - добавить методы динамически:
- Добавьте свойства в категорию, чтобы компилятор не жаловался слишком сильно.
- Клонировать подходящий IMP в +[NSObject resolInstanceMethod:]. Вам нужно будет ткнуть Objective-C во время выполнения.
Разработка на втором этапе:
Для каждого типа добавьте метод как
-(int)getEngineInt {
return (int()(id,SEL))(objc_msgSend)(engine, _cmd);
}
Обратите внимание, что для структур вам нужен objc_msgSend_stret, а для float /double вам может понадобиться objc_msgSend_fpret (я думаю, что он нужен только на i386; не уверен в AMD64). Простой взлом поддержки симулятора и устройства - это что-то вроде (я забываю имя макроса, которое использует GCC...)
#if __i386
#define objc_msgSend_fpret objc_msgSend
#endif
Сейчас реализовать +resolveInstanceMethod:
, вам нужно знать класс, который вы пересылаете заранее. Допустим, это двигатель.
+(BOOL)instancesRespondToSelector:(SEL)name
{
return [Engine instancesRespondToSelector:name];
}
+(BOOL)resolveInstanceMethod:(SEL)name
{
// Do we want to super-call first or last? Who knows...
if ([super resolveInstanceMethod:name]) { return YES; }
// Find the return type, which determines the "template" IMP we call.
const char * returntype = [Engine instanceMethodSignatureForSelector:name].methodReturnType;
if (!returnType) { return NO; }
// Get the selector corresponding to the "template" by comparing return types...
SEL template = NULL;
if (0 == strcmp(returntype,@encode(int))
{
sel = @selector(getEngineInt);
}
else if (0 == strcmp(Returntype,@encode(float))
{
...
}
if (!sel) { return NO; }
Method m = class_getInstanceMethod(self,template);
return class_addMethod(self, name, method_getImplementation(m), method_getTypeEncoding(m));
}
Кроме того, есть немного недокументированный метод -forwardingTargetForSelector: который может быть достаточно быстрым для ваших нужд.
РЕДАКТИРОВАТЬ: В качестве альтернативы, вы можете динамически перебирать свойства / методы. Кажется, не существует очевидного способа для анализа категорий, но вы можете определить их в протоколе, сделать что-то вроде @interface Engine:NSObject<Engine> ... @interface Car(DynamicEngine)<Engine>
и использовать objc_getProtocol("Engine")
а затем protocol_copyMethodDescriptionList()/protocol_copyPropertyList(), чтобы получить методы, а затем добавить получатели. Я не уверен, добавлены ли свойства в "список описания метода". Также обратите внимание, что функции "copy" не копируют методы / свойства из суперклассов, что (в данном случае) - это то, что вам нужно.
К сожалению, я не думаю, что свойства Objective-C 2.0 будут работать для вас, потому что я не думаю, что вы можете указать какой-либо вид пересылки в объявлении свойства.
Вы не можете иметь один макрос, который будет вставлять текст в двух разных местах. Однако вы можете использовать два макроса примерно так:
//This could also take the third argument and discard it, if you like
#define FORWARDI(type, prop) - (type)prop;
#define FORWARDM(type, prop, owner) - (type)prop { return owner.prop; }
//In the header...
FORWARDI(float, nPistons)
//In the implementation...
FORWARDM(float, nPistons, self.engine)
Если вы не возражаете против методов, не отображаемых в заголовочном файле (например, если вы будете использовать эти методы только внутри самой реализации класса), вы также можете использовать макрос файла реализации сам по себе.
Это не зависит от типа владельца, но должно работать с любым выражением.
Я приближаюсь к тому, что хочу. Остаются некоторые неприятные детали:
ForwardingInclude.h:
// no include guard; we want to be able to include this multiple times
#undef forward
#ifdef IMPLEMENTATION
#define forward(a, b, c) -(a) b { return [[self c] b]; }
#else
#define forward(a, b, c) -(a) b;
#endif
CarForwarding.h:
// again, no include guard
#include ForwardingInclude.h
forward(int, nPistons, engine)
forward(SparkPlug* sparkPlug, engine)
Car.h:
@interface Car: SomeSuperclass {
// some ivars
}
. . .
#include CarForwarding.h
Car.m:
. . .
@implementation Car
#define IMPLEMENTATION
#include CarForwarding.h
Ворчащие детали:
1) Мне не нравится эта строка #define IMPLEMENTATION. Я хочу, чтобы CarForwarding.h каким-то образом автоматически определял, включено ли оно в настоящее время в реализацию.
2) Было бы круто, если бы я мог, чтобы материал, определенный в файле пересылки, как-то также отображался в удобочитаемой форме в заголовке. Или, что еще лучше, запишите "прямые" определения непосредственно в файл Car.h, чтобы мне вообще не нужен файл CarForwarding.h.