【コード最適化】optional delegatesを呼び出す最適な方法

9603 ワード

【転載は出典を明記してください】http://www.cnblogs.com/lexingyu/p/3932475.html
本文は以下の2編のblogの総合脱水で、2人の作者が私たちの生産力を解放するために行った深い思考に感謝します=.Smart Proxy Delegation Elegant Delegation
delegateを使用するシナリオは通常こうです
classとdelegateの定義
@protocol TestObjectDelegate <NSObject>
@optional
- (void)testObjectMethod;
- (NSString *)testObjectMethodWithReturnValue;

@end


@interface TestObject : NSObject

@property (nonatomic, weak) id<TestObjectDelegate> delegate;

- (void)print;
- (void)printWithLog;

@end

クラスの内部でdelegateを呼び出す方法
- (void)print
{
    //call the delegate to do the real work
}

呼び出す方法は通常、次の2つです.
一般青年:
if ([self.delegate respondsToSelector:@selector(testObjectMethod)])
    {
        [self.delegate testObjectMethod];
    }

この方法の欠点は1)glue codeが大量に導入され,各optional functionに3行のコードが必要であることである.特にclangの-Warc-repeated-use-of-weakをオンにする場合にはselfを複数回使用する.delegate(通常はweak)は警告されます.【代码优化】调用optional delegates的最佳方法_第1张图片
だからそう書かなければならないかもしれません
- (void)print
{
    id <TestObjectDelegate> delegate = self.delegate;
    if ([delegate respondsToSelector:@selector(testObjectMethod)])
    {
        [delegate testObjectMethod];
    }
}

2)呼び出したメソッド名は2回書く必要があり、書き間違えてメソッドが呼び出されない可能性が高い.3)高周波呼び出しの方法としては,respondToSeletorを繰り返し呼び出す必要があり,性能に影響があることを意味する(RunTimeはrespondToSeletorをキャッシュする可能性があるため,ほとんどのアプリケーションで考慮する必要はない).
文芸青年が先にflagを加える
@interface TestObject : NSObject
{
    struct
    {
        unsigned int respond2TestObjectMethod:1;
    }_flags;
}

@property (nonatomic, weak) id<TestObjectDelegate> delegate;

- (void)print;

@end

setDelegateを再ロードしてflagを設定し、respondToSeletorの結果をキャッシュします.
- (void)setDelegate:(id<TestObjectDelegate>)delegate
{
    _delegate = delegate;
    
    BOOL respond2TestObjectMethod = [delegate respondsToSelector:@selector(testObjectMethod)];
    _flags.respond2TestObjectMethod = respond2TestObjectMethod ? 1 : 0;
}

最後にprintでキャッシュを直接使用した結果
- (void)print
{
    if (_flags.respond2TestObjectMethod)
    {
        [self.delegate testObjectMethod];
    }
}

この方法はAppleに広く採用されており,SDKでは随所に見られる.その利点はrespondToSeletorの結果を手動でキャッシュし、性能上の推測をする必要がなく、同時に-Warc-repeated-use-of-weakの警告を避けることです.しかし、残念なことに、コードの冗長性は除去されず、かえって深刻である(呼び出し時にglue codeが3行必要であり、ヘッダファイルとsetDelegateに大量のコードが追加されている).delegateのメソッド名を変更する必要がある場合は、悪夢のように複数のコードを同時に変更する必要があります.
うん.....申し訳ありませんが、ここには若者がいません.
外国の友人の考え
実際に私たちが本当に望んでいるのはこのようなものです
- (void)print
{
    [self.delegateProxy testObjectMethod];
}

glue codeも、その他の追加処理も、統一された場所に置きます.呼び出すとき、一言で分かりやすく、問題を解決します.では、具体的にはどうすればいいのでしょうか.実際、OCのメソッド呼び出し、あるいは正確には、メッセージングは、このようなメカニズムである.ここで、【代码优化】调用optional delegates的最佳方法_第2张图片について説明するために、前の自己描画図
OCのいずれかのメソッド呼び出しは,1からこのフローを歩み,1つのステップがだめなら次のステップに進む.すべての4つのステップが完了しても対応するimpletationが見つからない場合は、例外がトリガーされ、プログラムcrashがトリガーされます.簡単に言えば、各ステップの役割1)クラスのメソッドテーブル(methodList)では、seletorに基づいて対応するimpletationを検索する.2)resolveInstanceMethodは、coreを使用するなど、クラス内の類似の方法を集中的に処理するために使用される.
dataの場合はat dynamicとして複数のpropertyを指定する必要があります
これらのsetterとgetterはこの方法に集中することができます.3)forwardingTargetForSelectorは、本オブジェクトが処理できない呼び出し情報を別のオブジェクト処理に転送し、呼び出し情報を変更しない役割を果たす.4)forwardInvocationは、m ethodSignatureForSelectorや呼び出しパラメータなどの情報から生成されたNSInvocationに基づいて、1つのオブジェクトが今回の呼び出しを処理することを指定し、指定時に呼び出し情報を任意に変更することができ、例えばパラメータの個数を増やすことができる.
3は【代码优化】调用optional delegates的最佳方法_第3张图片と呼ばれ、それに応じて4はRegular message forwardingであり、両者が合わさってこそ完全なFast message forwardingである.
C言語は,関数を呼び出す際に,パラメータをレジスタやスタックに入れ,場合に応じて戻り値の空間を予約するために関数の原型を知る必要がある.OCはC言語のスーパーセットとしても考慮しなければならない.関数の呼び出し情報はOCにNSMethodSignatureとして存在し、Regular message forwardingにおいてmethodSignatureForSelectorによって返される.
以上の説明から明らかなように、1および2の役割はクラス内部でimpletationを探すことであり、3および4はクラス外部で適切な他のクラスのインスタンスを探して呼び出し情報を処理することである.明らかに、3と4はdelegateProxyに必要なものです.
こんなにたくさん敷いて、やっと本題に着いた.
Message forwardingメカニズムを使用してdelegateProxyを構築
ここではdelegateProxyとしてMessage forwardingの派生クラスを構築した.
@interface CDDelegateProxy : NSProxy

@property (nonatomic, weak, readonly) id delegate;
@property (nonatomic, strong, readonly) Protocol *protocol;
@property (nonatomic, strong, readonly) NSValue *defaultReturnValue;

@end

delegateProxyには、プロキシされたdelegateオブジェクト、delegateに対応するprotocol、メソッドが見つからない場合に提供されるデフォルト値がそれぞれ保存されます.はい.mファイルには、まずglue codeを入れます.
//        
- (BOOL)respondsToSelector:(SEL)selector
{
    return [_delegate respondsToSelector:selector];
}

//Fast message forwarding,   glue code
- (id)forwardingTargetForSelector:(SEL)selector
{
    id delegate = _delegate;
    return [delegate respondsToSelector:selector] ? delegate : self;
}

うん...これでおしまいになりそうだ.ほとんどの場合、確かにそうです.ただし、メソッドが存在せず、デフォルトの戻り値が必要な場合は、たとえば
- (void)printWithLog
{
    //      delegateProxy ,  
    NSString *logInfo = [self.delegateProxy testObjectMethodWithReturnValue];
    NSLog(@"%@", logInfo);
}

Regular message forwardingが必要です.具体的なやり方は以下の通りです.
//Regular message forwarding
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    id delegate = _delegate;
    NSMethodSignature *signature = [delegate methodSignatureForSelector:selector];
    
    // delegate       ,  protocol      MethodSignature
    if (!signature)
    {
        if (!_signatures) _signatures = [self methodSignaturesForProtocol:_protocol];
        signature = CFDictionaryGetValue(_signatures, selector);
    }
    
    //    return nil,      forwardInvocation
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    //       invocation         ,       
    if (_defaultReturnValue
        && strcmp(_defaultReturnValue.objCType, invocation.methodSignature.methodReturnType) == 0)
    {
        char buffer[invocation.methodSignature.methodReturnLength];
        [_defaultReturnValue getValue:buffer];
        [invocation setReturnValue:&buffer];
    }
}

まずmethodSignatureForSelectorがprotocolのメソッドに従って宣言し、signatureを返し、forwardInvocationによってデフォルトの戻り値のタイプが一致するかどうかを判断し、一致するとプリセットのデフォルト値(すなわち、先ほど述べたdefaultReturnValue)を返します.
これでdelegateProxyが構築されます.使用するときはdelegateProxyの役割はクラス内部で呼び出しを簡潔に保つだけで、外部コードにとって透明であるべきであることに注意してください.具体的には、まずdeleagteProxyをclass extensionに定義する必要があります.
//.m   
@interface SomeObject ()<TestObjectDelegate>

@property (nonatomic, strong) id<TestObjectDelegate> delegateProxy;

@end

ここでdelegateProxyをidとして直接宣言します
の形式であり、その後も符号化時にXcodeによるprotocol中の方法の自動提示補完を享受できるようにすることを目的とする.
次にoverride delegate(真id)
ヘッダファイルに定義されています)
- (void)setDelegate:(id <TestObjectDelegate>)delegate 
{
  self.delegateProxy = delegate ? (id <TestObjectDelegate>)[[CDDelegateProxy alloc] initWithDelegate:delegate] : nil;
}
- (id <TestObjectDelegate>)delegate
 {
  return ((CDDelegateProxy *)self.delegateProxy).delegate;
}

この手順は、マクロで簡単にできます.例えば、
#define CD_DELEGATE_PROXY_CUSTOM(protocolname, GETTER, SETTER) \
- (id<protocolname>)GETTER { return ((PSTDelegateProxy *)self.GETTER##Proxy).delegate; } \
- (void)SETTER:(id<protocolname>)delegate { self.GETTER##Proxy = delegate ? (id<protocolname>)[[PSTDelegateProxy alloc] initWithDelegate:delegate conformingToProtocol:@protocol(protocolname) defaultReturnValue:nil] : nil; }

#define CD_DELEGATE_PROXY(protocolname) PST_DELEGATE_PROXY_CUSTOM(protocolname, delegate, setDelegate)

使用中は簡単に
CD_DELEGATE_PROXY(id <PSPDFResizableViewDelegate>)

もちろん,比較的パーソナライズされたdelegateの名前は,このマクロを拡張することによって実現できる.
これにより、delegateに外部からアクセスしたときも、取得されたオブジェクトは正しいオブジェクトです.以上,optional delegatesを呼び出す最良の方法であり,起因から原理からソリューションへの完全な説明である.
説明しやすいように、自分で書いた簡略版のdelegateProxyを使っています.ここにはNSProxyを提供しています.勉強に値する点がたくさんありますよ.
やっと書き終わりました!!!