13

NSProxy サブクラスをセットアップして、選択した UIView の代わりになるように設定することで、UIViews に機能を追加する (状態に応じて CALayers を構成する) ことを実験しています。これが私が試したことです:

私の NSProxy サブクラスには、次のコードがあります。

#pragma mark Initialization / Dealloc

- (id)initWithView:(UIView *)view
{
    delegate = view;
    [delegate retain];

    return self;
}

- (void)dealloc
{
    [delegate release];
    [super dealloc];
}


#pragma mark Proxy Methods

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation setTarget:delegate];
    [anInvocation invoke];
    return;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    return [delegate methodSignatureForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector 
{
    BOOL rv = NO;

    if ([delegate respondsToSelector:aSelector]) { rv = YES; }

    return rv;
}

そして、私の NSProxy サブクラスを次のように使用します。

UILabel *label = [[HFMultiStateProxy alloc] initWithView:[[[UILabel alloc] initWithFrame:cellFrame] autorelease]];
label.text = text;
label.font = font;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.opaque = NO;

[self addSubview:label];

addSubview: 行に到達するまで動作するようです。

メッセージ トレースをオンにすると ( instrumentObjcMessageSends(YES); )、addSubview: の奥深くまで機能する前の各メッセージの転送が表示されます。この一連のメソッド呼び出しがログに表示されます (ここに表示されている最初のメッセージは、プロキシー):

- UILabel UIView _makeSubtreePerformSelector:withObject:
- UILabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- NSMethodSignature NSMethodSignature methodReturnType
- NSMethodSignature NSMethodSignature _argInfo:
- NSMethodSignature NSMethodSignature _frameDescriptor
+ UILabel NSObject resolveInstanceMethod:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject class
- UILabel NSObject doesNotRecognizeSelector:

そして、次のエラーが表示されます。

2011-02-20 16:38:52.048 FlashClass_dbg[22035:207] -[UILabel superlayer]: unrecognized selector sent to instance 0x757d470

NSProxy サブクラスを使用せず、代わりに UILabel サブクラス (HFMultiStateLabel) を使用すると、問題なく動作します。addSubview: が呼び出されると発生するメッセージ トレースを次に示します (HFNoteNameControl はラベルのスーパービューです)。

- HFNoteNameControl UIView addSubview:
- HFNoteNameControl UIView _addSubview:positioned:relativeTo:
- HFMultiStateLabel UIView superview
- HFMultiStateLabel UIView window
- HFNoteNameControl NSObject isKindOfClass:
- HFNoteNameControl NSObject class
- HFNoteNameControl UIView window
- UIWindow NSObject isKindOfClass:
- UIWindow NSObject class
- HFNoteNameControl UIView _shouldTryPromoteDescendantToFirstResponder
- HFMultiStateLabel UIView _isAncestorOfFirstResponder
- HFMultiStateLabel UIView _willMoveToWindow:withAncestorView:
- HFMultiStateLabel UIView _willMoveToWindow:
- HFMultiStateLabel UIView willMoveToWindow:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- HFMultiStateLabel UIView willMoveToSuperview:
- HFMultiStateLabel UIView _unsubscribeToScrollNotificationsIfNecessary:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- CALayer CALayer superlayer

NSProxy を使用すると、-superlayer までの各メソッドが正常に呼び出されることを確認できます。何らかの理由で、NSProxy では、CALayer の代わりに UILabel 上のスーパーレイヤーが呼び出されています。おそらくどこかで混乱し、UILabel がその CALayer の代わりにサブレイヤーに挿入されるのでしょうか?

何か不足していますか?

UIKit は、NSProxy がフックする通常のメカニズムをバイパスする何らかの最適化を行いますか?

他の考え?

ありがとう!

ヘンリー

PS私はこれをデバイスではなくシミュレータでのみ試しました。その行動は何か違うでしょうか?

4

4 に答える 4

3

私はあきらめました。NSProxy は十分に活用されていないオブジェクトであり、Apple の例を超えて使用できる可能性があるという結論に達しました。要するに、NSProxy は、サブクラス化やカテゴリの追加なしでオブジェクトの機能を拡張する一般的な方法として使用する準備ができていないと思います。

昔は、目的の機能を実装するために poseAsClass 呼び出しを使用していました。

私の解決策は次のようになりました:

  • 追加のプロパティを追加する UIView にカテゴリを追加しました。これらのプロパティの実装は、set & get メッセージを UIView の「addOn」プロパティに転送し、これもカテゴリに入れました。もちろん、UIView のカテゴリ実装におけるこの「addOn」プロパティのデフォルト値は nil です。(任意の UIView に AddOn インスタンスを関連付けることができるように静的ハッシュ テーブルを実装することもできましたが、保持カウントを適切に管理するのは危険な策略だと思いました。)

  • 「AddOn」クラスには、UIView を直接操作するための余分なコードがあり、余分な描画コードが追加されていました。

  • この追加機能を追加したい UIView のタイプごとに、次のようなコードでサブクラス化する必要がありました。a) 「AddOn」クラスのインスタンス メソッドと対応するプロパティ コードを作成しました。b) 「 AddOn」コードは、その機能を追加するチャンスです。

  • これらの各サブクラスには、必要な機能を AddOn インスタンスに転送するために、本質的に同じコードが含まれています。

だから、私はできる限りコードの重複を最小限に抑えることになりましたが、「アドオン」機能の使用を可能にする UIView の子孫サブクラスのそれぞれは、コードの重複を引き起こします。

クラス メソッド操作関数を使用することで、コードの重複をさらに最小限に抑えることができたようですが、学習曲線とコードのさらなる難読化により、その道をたどることが思いとどまりました。

于 2011-04-16T14:28:15.183 に答える
2

ビューで NSProxy を使用したことはありませんが、カスタム ビュー クラスを使用して別のビューを表示することで同様のことを行いました。おそらく、システムにはプロキシ オブジェクトではなく、実際のビューが必要です。「プロキシ」ビューを使用するには、次の 2 つの方法があります。

  1. プロキシ ビューをプロキシ ビューのサブビューにします。プロキシは、プロキシされたビューからフレーム、自動サイズ変更マスクなどを取得し、プロキシされたビューをサブビューとして追加し、そのフレームをプロキシ ビューの境界に設定し、その自動サイズ変更マスクを常にプロキシ ビューを満たすように設定します。プロキシ ビューが削除されると、すべての設定がプロキシ ビューからそこにコピーされます。プロキシにコピーされないプロパティは、転送を使用してプロキシ ビューに渡されます。

  2. プロキシ ビューは、ほとんどすべてのメッセージをプロキシ ビューに渡します。プロキシ ビューは、lock/unlockFocus、display などのメソッドをオーバーライドしません。drawRect: をオーバーライドして、プロキシされたビューで drawRect: を呼び出します。

于 2011-02-21T01:59:08.187 に答える