iOS - использовать макросы для пересылки кучу сообщений? - PullRequest
1 голос
/ 28 июня 2011

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);

В идеале, чтобы макросы можно было многократно использовать, если бы позже я захотел позже использовать аналогичную стратегию для получения свойств лица FirstName, lastName, placeOfBirth и dateOfBirth из его или ее BirthCertificate.

Ответы [ 3 ]

1 голос
/ 28 июня 2011

Возможно, проще всего динамически добавить методы:

  • Добавьте свойства в категорию, чтобы компилятор не жаловался слишком много.
  • Клонировать подходящий IMP в + [NSObject resolInstanceMethod:] . Вам нужно будет запустить Objective-C runtime .

Разработка второго шага:

Для каждого типа добавьте метод, такой как

-(int)getEngineInt {
  return (int()(id,SEL))(objc_msgSend)(engine, _cmd);
}

Обратите внимание, что для структур вам нужен objc_msgSend_stret, а для чисел с плавающей запятой / двойников может нужен 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» не копируют методы / свойства из суперклассов, что (в данном случае) - то, что вам нужно.

0 голосов
/ 28 июня 2011

Я приближаюсь к тому, что хочу. Остаются некоторые неприятные детали:

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.

0 голосов
/ 28 июня 2011

К сожалению, я не думаю, что свойства 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)

Если вы не возражаете, методы не отображаются в заголовочном файле (например, если вы будете использовать эти методы только внутри самой реализации класса)), вы также можете использовать макрос файла реализации сам по себе.

Это не зависит от типа владельца, но должно работать с любым выражением.

...