如果目标支持它,只执行选择器?

时间:2010-11-23 10:34:57

标签: objective-c protocols

如何调用可选的协议方法?

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

现在我们每次要拨打doAdoB时都要检查:

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

那太傻了。我在NSObject上提出了一个类别:

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

......这不是那么好。你有一个更智能的解决方案,或者你是否只是在每次通话前忍受条件?

5 个答案:

答案 0 :(得分:6)

我不完全确定我理解你的反对是诚实的。据我所知,代码完全符合您对可选方法的期望,并且只需要很少的额外措辞。我不认为你的类别让你的意图更清楚。

对您的第一个选项的唯一更改是这样做:

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

答案 1 :(得分:3)

您的第二个选项等同于使所需的可选方法,然后编写它的空实现。

第一种方法是正确的方法。由于某种原因,可选方法是可选的,如果调用代码不可用,您的调用代码可能希望执行不同的操作。

答案 2 :(得分:2)

拦截调用的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];
}

您可以轻松添加对合并协议和诸如此类的检查,但对于所述情况,这应该已经有效。

答案 3 :(得分:1)

您的类别还可以,但非常不灵活。委托回调应该几乎总是包含至少一个参数(调用对象),并且您的方法不允许参数。委托方法也经常返回值,这种方法也不允许这样做。

正如Stephen所说,正确的代码不应该使用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可以克服这个问题。当然,如果将未知方法传递给您的委托,它也会抛弃编译时警告。但是,您仍然会遇到运行时异常。

答案 4 :(得分:1)

类别的问题是它会自动保留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] );