Only perform a selector if the target supports it?

What do you call additional protocol methods?

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

Now we must check every time we want to call doAor doB:

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

This is just stupid. Ive come up with a category on NSObjectthat adds:

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

... which is not much better. Do you have a more reasonable solution, or do you simply agree to the conventions before each call?

+3
source share
5 answers

What about the NSObject category that intercepts the call? Using MAObjCRuntime, it will look something like this:

- (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 %@ 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];
}

You can easily add a check of the built-in protocols and much more, but for the specified case, this should already work.

+2

, , . , , , . , .

- :

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

, .

. - , - , .

+3

, . ( ), . , .

, performSelector:, . , "Undeclared Selector" (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 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 . , , . .

+1
source

The problem with the category for this is that it is automatically executed for all calls in NSObject. I would solve this with a macro as shown below:

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

Used as follows:

id <SomeProtocol> delegate = ...;

//Call the optional protocol method
BM_PERFORM_IF_RESPONDS( [delegate doOptionalProtocolMethod:arg] );
+1
source

Source: https://habr.com/ru/post/1776423/


All Articles