Только выполнить селектор, если цель поддерживает это? - PullRequest
3 голосов
/ 23 ноября 2010

Как вы называете необязательные методы протокола?

@protocol Foo
@optional
- (void) doA;
- (void) doB;
@end

Теперь мы должны проверять каждый раз, когда мы хотим позвонить doA или doB:

if ([delegate respondsToSelector:@selector(doA)])
    [delegate performSelector:@selector(doA)];

Это просто глупо. Я выбрал категорию на NSObject, которая добавляет:

- (void) performSelectorIfSupported: (SEL) selector
{
    if ([self respondsToSelector:selector])
        [self performSelector:selector];
}

… что не намного лучше. У вас есть более разумное решение или вы просто миритесь с условиями перед каждым звонком?

Ответы [ 5 ]

6 голосов
/ 23 ноября 2010

Я не совсем уверен, что понимаю ваше возражение, если честно. Насколько я вижу, код делает именно то, что вы ожидаете от необязательного метода, и делает это с очень небольшим лишним словоблудием. Я не думаю, что ваша категория делает ваши намерения более ясными.

Единственное изменение в вашем первом варианте - это сделать так:

if ([delegate respondsToSelector:@selector(doA)])
    [delegate doA];
3 голосов
/ 23 ноября 2010

Ваш второй вариант эквивалентен обязательному выполнению необязательного метода и написанию его нулевой реализации.

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

2 голосов
/ 23 ноября 2010

Как насчет категории NSObject, которая перехватывает вызов? Используя MAObjCRuntime, это выглядело бы примерно так:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
  id target = [anInvocation target];
  SEL selector = [anInvocation selector];

  for(RTProtocol *protocol in [[target class] rt_protocols])
  {
    // check optional instance methods
    NSArray *methods = [protocol methodsRequired:NO instance:YES];
    for (RTMethod *method in methods)
    {
      if ([method selector] == selector)
      {
        // NSLog(@"target %@'s protocol %@ contains selector %@", target, protocol, NSStringFromSelector(selector));
        // just drop the invocation
        return;
      }
    }
  }

  // selector does not seem to be part of any optional protocol
  // use default NSObject implementation:
  [self doesNotRecognizeSelector:selector];
}

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

1 голос
/ 16 октября 2014

Проблема с категорией для этого состоит в том, что она автоматически сохраняется для всех вызовов на NSObject.Я бы решил это с помощью макроса, как показано ниже:

#define BM_PERFORM_IF_RESPONDS(x) { @try { (x); } @catch (NSException *e) { if (![e.name isEqual:NSInvalidArgumentException]) @throw e; }}

Для использования следующим образом:

id <SomeProtocol> delegate = ...;

//Call the optional protocol method
BM_PERFORM_IF_RESPONDS( [delegate doOptionalProtocolMethod:arg] );
1 голос
/ 23 ноября 2010

Ваша категория в порядке, но крайне негибкая.Делегированные обратные вызовы почти всегда должны включать хотя бы один параметр (вызывающий объект), и ваш подход не учитывает параметры.Методы делегатов также часто возвращают значения, и этот подход также не допускает этого.

Как заметил Стивен, правильный код не должен использовать performSelector:, а просто вызывать метод напрямую.Это имеет преимущество проверки во время компиляции на наличие опечаток, особенно если в сочетании с параметром предупреждения «Необъявленный селектор» (GCC_WARN_UNDECLARED_SELECTOR), который я настоятельно рекомендую.

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

#import <objc/runtime.h>

@interface RNDelegateTrampoline : NSObject {
@private
    id delegate_;
    Protocol *protocol_;
}
@property (nonatomic, readwrite, assign) id delegate;
@property (nonatomic, readwrite, retain) Protocol *protocol;
- (id)initWithProtocol:(Protocol *)aProtocol delegate:(id)aDelegate;
@end

@implementation RNDelegateTrampoline

- (id)initWithProtocol:(Protocol *)aProtocol delegate:(id)aDelegate {
    if ((self = [super init])) {
        protocol_ = [aProtocol retain];
        delegate_ = aDelegate;
    }
    return self;
}

- (void)dealloc {
    [protocol_ release], protocol_ = nil;
    delegate_ = nil;
    [super dealloc];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    // Look for a required method
    struct objc_method_description desc = protocol_getMethodDescription(self.protocol, selector, YES, YES);
    if (desc.name == NULL) {
        // Maybe it's optional
        desc = protocol_getMethodDescription(self.protocol, selector, NO, YES);
    }
    if (desc.name == NULL) {
        [self doesNotRecognizeSelector:selector];   // Raises NSInvalidArgumentException
        return nil;
    }
    else {
        return [NSMethodSignature signatureWithObjCTypes:desc.types];
    }   
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    if ([[self delegate] respondsToSelector:[invocation selector]]) {
        [invocation invokeWithTarget:[self delegate]];
    }
}
@synthesize delegate = delegate_;
@synthesize protocol = protocol_;
@end

Затем вы использовали бы его следующим образом:

@property (nonatomic, readwrite, retain) id delegateTramp;

self.delegateTramp = [[[RNDelegateTrampoline alloc] initWithProtocol:@protocol(ThisObjectDelegate) delegate:aDelegate] autorelease];

...

[self.delegateTramp thisObject:self didSomethingWith:x];

Обратите внимание, что мы использовали id вместо RNDelegateTrampoline в качестве типа батута нашего делегата.Это важно, иначе вы получите предупреждения компилятора обо всем, что вы пытаетесь отправить на него.Объявление его как id преодолевает это.Конечно, он также отбрасывает предупреждения во время компиляции, если вы передаете неизвестный метод вашему делегату.Вы все равно получите исключение во время выполнения.

...