Reputation: 2797
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?
Upvotes: 8
Views: 8790
Reputation: 25
On top of answer provided by @micantox I would add that instead of struct
you could use NS_OPTIONS
to define a bitmask
.
typedef NS_OPTIONS (NSUInteger, MyDelegateOptions)
{
MyDelegateOptionDidFinishedWithTaskOne = 1 << 0,
MyDelegateOptionDidFinishedWithTaskTwo = 1 << 1,
MyDelegateOptionDidFinishedWithTaskThree = 1 << 2
};
You could then create property that can hold bitmask
:
@property (nonatomic, assign) MyDelegateOptions delegateOptions;
and do check if delegate respondsToSelector:
in your setter like
- (void)setDelegate:(id<MyDelegateProtocol>)delegate
{
_delegate = delegate;
if ([delegate respondsToSelector:@selector(myDelegateDidFinishedWithTaskOne:)]) {
{
self.delegateOptions = self.delegateOptions | MyDelegateOptionDidFinishedWithTaskOne;
}
if ([delegate respondsToSelector:@selector(myDelegateDidFinishedWithTaskTwo:)]) {
{
self.delegateOptions = self.delegateOptions | MyDelegateOptionDidFinishedWithTaskTwo;
}
if ([delegate respondsToSelector:@selector(myDelegateDidFinishedWithTaskThree:)]) {
{
self.delegateOptions = self.delegateOptions | MyDelegateOptionDidFinishedWithTaskThree;
}
}
Then you can simply check assigned bit patterns by using bitwise AND
if (self.delegateOptions & MyDelegateOptionDidFinishedWithTaskOne) {
[self.delegate myDelegateDidFinishedWithTaskTwo:myResult];
}
As you can see use of bitwise AND
will return true
if bit for MyDelegateOptionDidFinishedWithTaskOne
is set as 1 even when other bits are set as well.
So if you assume that delegate
was checked in your setter and based on this your delegateOptions
holds bit pattern of 00000111 and your MyDelegateOptionDidFinishedWithTaskOne
represents bit pattern of 00000001 then by simply using bitwise AND
00000111 & 00000001 = 00000001
you'll get true
with your conditional statement.
It is worth notting that this is overkill in situations when you need to check your delegate
only once or twice.
Upvotes: 1
Reputation: 5616
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.
Upvotes: 13
Reputation: 299355
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.Upvotes: 4