代码之家  ›  专栏  ›  技术社区  ›  zoul

仅当目标支持时才执行选择器?

  •  3
  • zoul  · 技术社区  · 15 年前

    如何调用可选协议方法?

    @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 回复  |  直到 15 年前
        1
  •  2
  •   w-m    15 年前

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

    您可以很容易地添加对合并的协议和其他内容的检查,但是对于所述的情况,这应该已经起作用了。

        2
  •  6
  •   Stephen Darlington    15 年前

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

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

    if ([delegate respondsToSelector:@selector(doA)])
        [delegate doA];
    
        3
  •  3
  •   Alex Brown    15 年前

    第二个选项相当于使可选方法成为必需的,然后编写它的空实现。

    第一种方法是正确的。出于某种原因,可选方法是可选的,如果调用代码不可用,则可能需要执行其他操作。

        4
  •  1
  •   Rob Napier    15 年前

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

    正如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 作为我们的代表蹦床类型。这一点很重要,否则您将收到编译器对您试图发送给它的所有内容的警告。声明为 身份证件 克服了这一点。当然,如果将未知方法传递给委托,它也会丢弃编译时警告。不过,您仍然会得到一个运行时异常。

        5
  •  1
  •   Werner Altewischer    11 年前

    这个类别的问题是,它自动保存对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] );