2010-11-23 51 views
3

你如何調用可選的協議方法?只有目標支持它時才執行選擇器?

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

現在,我們有我們想要調用doAdoB每次檢查:

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

這只是愚蠢。我想出了在NSObject類別,增加了:

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

...這是不是要好得多。你有更聰明的解決方案嗎,還是每次打電話之前都要忍受條件限制?

回答

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]; 
} 

你可以輕鬆地添加檢查合併協議和諸如此類的東西,但對於規定的情況下,這應該已經工作。

+0

這是我想到的「更智能的解決方案」(儘管這很可能不值得麻煩)。謝謝! – zoul 2010-11-23 16:01:57

3

您的第二個選項等效於使所需的可選方法,然後編寫它的空實現。

第一種方法是正確的。可選方法由於某種原因是可選的,如果它們不可用,您的調用代碼可能想要做一些不同的事情。

+0

嗯,我通常使用回調代表團,像`thisHappened`和`thatHappened`,我將通過類似以下的宏解決問題代表並不一定對它們都感興趣。在這種情況下,第二個選項可以節省打字,而不是帶條件的打字。 – zoul 2010-11-23 10:45:47

6

我不完全相信我明白你的反對誠實。就我所見,代碼完全符合您期望的可選方法,並且只需要很少的額外語言。我認爲你的類別不會讓你的意圖更清晰。

你的第一個選項的唯一變化是做這樣的:

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

唯一的異議是每個可選代表呼叫之前的額外條件。我希望可選調用在委託沒有實現時自動失敗,以保存額外的代碼。 (不知道爲什麼performSelector爬到那裏,謝謝。) – zoul 2010-11-23 16:07:22

1

您的類別是確定的,但非常靈活。委託回調應該至少包含至少一個參數(調用對象),並且您的方法不允許使用參數。委託方法也經常返回值,這種方法也不允許。

正如Stephen指出的,正確的代碼不應該使用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'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克服了這一點。當然,如果將未知方法傳遞給委託,它也會拋出編譯時警告。不過,您仍然會遇到運行時異常。

+0

也是一個不錯的解決方案,謝謝。 – zoul 2010-11-23 16:03:29

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]); 
相關問題