I've got used wrapping sending @optional
delegate's methods into:
if ([self.delegate respondsToSelector:@selector(method:)]) {
[self.delegate method:obj];
}
It works well, but if there are lot of delegate's methods, it looks like duplicating respondToSelector:
code...
At some point of time I've put respondsToSelector:
to separate method:
//ignore warning
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
- (void)performDelegateSelector:(SEL)selector withObject:(id)obj {
if ([self.delegate respondsToSelector:selector]) {
[self.delegate performSelector:selector withObject:obj];
}
}
As a result I have just one respondToSelector: check, but It still doesn't look like well improved.
[self performDelegateSelector:@selector(method:) withObject:self];
What do you think? Does it make sense in using some helpers or category to wrap sending all @optional
delegate's methods, or it's something that shouldn't be improved?
Performance-wise it depends on what use you make of the delegate, whether it's directly affecting the UI (in which case you want to have faster response times) etc.
A good speed optimization is to register all of the methods implemented by the delegate in a data structure when you first set the delegate. You could do:
struct {
unsigned int myFirstMethod:1;
unsigned int mySecondMethod:1;
} delegateRespondsTo;
-(void)setDelegate:(id<MyProtocol>)delegate
{
self.delegate = delegate;
delegateRespondsTo.myFirstMethod = [delegate respondsToSelector:@selector(myFirstMethod)];
delegateRespondsTo.mySecondMethod = [delegate respondsToSelector:@selector(mySecondMethod)];
}
Then you can simply do
if ( delegateRespondsTo.myFirstMethod )
[self.delegate myFirstMethod];
Check this great answer for a more exhaustive explanation.
You can use a trampoline for this. A trampoline is an object that forwards messages to another object. Here's a simple DelegateTrampoline.
#import <objc/runtime.h>
@interface DelegateTrampoline : NSObject
- (id)initWithProtocol:(Protocol *)protocol delegate:(id)delegate;
@end
#import "DelegateTrampoline.h"
@interface DelegateTrampoline ()
@property (nonatomic) Protocol *protocol;
@property (nonatomic, weak) id delegate;
@end
@implementation DelegateTrampoline
- (id)initWithProtocol:(Protocol *)protocol delegate:(id)delegate {
self = [super init];
if (self) {
_protocol = protocol;
_delegate = delegate;
}
return self;
}
- (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 {
SEL selector = [invocation selector];
if ([self.delegate respondsToSelector:selector]) {
[invocation setTarget:self.delegate];
[invocation invoke];
}
}
@end
Here's how you would use it:
@interface MyObject ()
@property (nonatomic) id delegateTrampoline;
@end
...
self.delegateTrampoline = [[DelegateTrampoline alloc] initWithProtocol:@protocol(MyProtocol)
delegate:delegate];
[self.delegateTrampoline myobject:self didSomethingAtIndex:1];
@end
Some notes:
NSInvocation
object, which can be hundreds of times slower than a simple method call. That said, unless you're calling your delegate method in a tight loop, it's probably not a problem.delegateTrampoline
to be of type id
. This allows you to pass arbitrary selectors to it. But it also means that the compiler cannot detect if you pass a selector to delegateTrampoline
that is not in the protocol. You'll crash at runtime if you do this. The compiler can detect if you pass a totally unknown selector, so it'll catch simple typos.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With