8

@optionalデリゲートのメソッドを次のようにラッピングすることに慣れました。

if ([self.delegate respondsToSelector:@selector(method:)]) {
    [self.delegate method:obj];
}

それはうまく機能しますが、デリゲートのメソッドがたくさんある場合、respondToSelector:コードを複製しているように見えます...

ある時点で、私はrespondsToSelector:別の方法に入れました:

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

その結果、respondToSelector: チェックは 1 つしかありませんが、まだ十分に改善されているようには見えません。

[self performDelegateSelector:@selector(method:) withObject:self];

どう思いますか?いくつかのヘルパーまたはカテゴリを使用してすべてのデリゲートのメソッドの送信をラップすることは理にかなっています@optionalか?それとも改善すべきではないことですか?

4

3 に答える 3

4

これにはトランポリンを使用できます。トランポリンは、メッセージを別のオブジェクトに転送するオブジェクトです。これは単純な 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

使用方法は次のとおりです。

@interface MyObject ()
@property (nonatomic) id delegateTrampoline;
@end
...
self.delegateTrampoline = [[DelegateTrampoline alloc] initWithProtocol:@protocol(MyProtocol)
                                                              delegate:delegate];

[self.delegateTrampoline myobject:self didSomethingAtIndex:1];
@end

いくつかのメモ:

  • これは、他のアプローチに比べて非常に遅いです。オブジェクトを作成する必要がありNSInvocation、単純なメソッド呼び出しよりも数百倍遅くなる可能性があります。とはいえ、デリゲート メソッドをタイトなループで呼び出していない限り、おそらく問題にはなりません。
  • delegateTrampoline型であることを宣言する必要がありidます。これにより、任意のセレクターを渡すことができます。delegateTrampolineただし、プロトコルにないセレクターを渡すかどうかをコンパイラーが検出できないことも意味します。これを行うと、実行時にクラッシュします。コンパイラは、まったく未知のセレクターを渡すかどうかを検出できるため、単純なタイプミスをキャッチします。
于 2013-09-23T13:58:42.667 に答える
1

@micantoxから提供された回答に加えstructNS_OPTIONSbitmask.

typedef NS_OPTIONS (NSUInteger, MyDelegateOptions) 
{ 
  MyDelegateOptionDidFinishedWithTaskOne = 1 << 0,
  MyDelegateOptionDidFinishedWithTaskTwo = 1 << 1,
  MyDelegateOptionDidFinishedWithTaskThree = 1 << 2
};

次に、保持できるプロパティを作成できますbitmask

@property (nonatomic, assign) MyDelegateOptions delegateOptions;

respondsToSelector:セッターでデリゲートが好きかどうかを確認してください

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

次に、割り当てられたビットパターンを簡単に確認できますbitwise AND

if (self.delegateOptions & MyDelegateOptionDidFinishedWithTaskOne) {
    [self.delegate myDelegateDidFinishedWithTaskTwo:myResult];
}

ご覧のとおり、他のビットが設定されている場合でも、ビット forが1bitwise ANDに設定されている場合は、の使用が返されます。trueMyDelegateOptionDidFinishedWithTaskOne

したがって、それdelegateがセッターでチェックされ、これに基づいて00000111delegateOptionsのビット パターンを保持し、 00000001のビット パターンを表すと仮定すると、単純にMyDelegateOptionDidFinishedWithTaskOnebitwise AND

00000111 & 00000001 = 00000001

true条件文で取得できます。

delegate1 回か 2 回だけチェックする必要がある状況では、これはやり過ぎであることに注意してください。

于 2016-02-19T10:24:45.767 に答える