如何调用可选的协议方法?
@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];
}
......这不是那么好。你有一个更智能的解决方案,或者你是否只是在每次通话前忍受条件?
答案 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] );