問題の核心は、Key-Value Observingの内臓がに存在しNSObject
、NSProxy
から継承されないことですNSObject
。NSProxy
どのアプローチでも、オブジェクトが独自の遵守リスト(つまり、外部の人々がそれについて観察することを望んでいるもの)を保持する必要があると私は合理的に確信しています。これだけで、NSProxyの実装にかなりの重みが加わります。
ターゲットを観察する
プロキシのオブザーバーに実際のオブジェクトを実際に監視させようとしたようです。つまり、ターゲットに常にデータが入力されていて、すべての呼び出しをターゲットに転送しただけの場合は、転送addObserver:...
とremoveObserver:...
呼び出しも行われます。これに伴う問題は、あなたが次のように言うことから始めたことです。
NSProxyは、まだ存在していないオブジェクトの代用オブジェクトとして非常にうまく機能しているようです。
完全を期すために、このアプローチの根幹のいくつかと、それが機能しない理由(少なくとも一般的なケースでは)について説明します。
これが機能するためには、NSProxy
サブクラスは、ターゲットが設定される前に呼び出された登録メソッドの呼び出しを収集し、ターゲットが設定されたときにそれらをターゲットに渡す必要があります。削除も処理する必要があると考えると、これはすぐに厄介になります。後で削除された監視を追加することは望ましくありません(監視オブジェクトの割り当てが解除されている可能性があるため)。また、観測を追跡する方法でオブザーバーを保持したくない場合もあります。これにより、意図しない保持サイクルが発生しないようにします。処理する必要のあるターゲット値の次の可能な遷移が表示されます
- ターゲットは
nil
初期化されていましたが、nil
遅くなりません
- ターゲットが設定されていない
nil
、nil
後でなります
- ターゲットが非値に設定された後、別の非値
nil
に変更されましたnil
- ターゲットは
nil
(初期化されていない)、nil
後でないようになります
...そして、ケース#1の場合はすぐに問題が発生します。KVOオブザーバーがobjectValue
(常にプロキシになるため)監視するだけであれば、おそらくここで大丈夫ですが、オブザーバーがプロキシ/実オブジェクトを通過するkeyPathを監視したとしましょうobjectValue.status
。これは、KVO機構がvalueForKey: objectValue
観測のターゲットを呼び出してプロキシを取り戻した後、プロキシを呼び出しvalueForKey: status
てnil
戻ってきたことを意味します。ターゲットが非になるとnil
、KVOはその値がその下から変更されたと見なし(つまり、KVOに準拠していない)、引用したエラーメッセージが表示されます。ターゲットを一時的に強制的に戻す方法がある場合は、nil
そのstatus
動作をオンにして、-[target willChangeValueForKey: status]
、動作をオフにしてから、を呼び出します-[target didChangeValueForKey: status]
。とにかく、同じ落とし穴があるので、ケース#1でここで停止できます。
nil
あなたがそれを呼び出しても何もしませんwillChangeValueForKey:
(つまり、KVO機械はへの移行中またはからの移行中にその内部状態を更新することを決して知りませんnil
)
- ターゲットオブジェクトに、一時的に嘘をついてvalueForKeyから戻るメカニズムを強制する
nil
:すべてのキーについて、指定された要望が「透過プロキシ」である場合、かなり面倒な要件のように思われます。
nil
ターゲットを持つプロキシでsetValue:forKey:を呼び出すとはどういう意味ですか?それらの値を維持しますか?本当のターゲットを待っていますか?投げますか?巨大な未解決の問題。
このアプローチに対する1つの可能な変更は、実際のターゲットがnil
おそらく空の場合にサロゲートターゲットを使用し、 NSMutableDictionary
KVC/KVO呼び出しをサロゲートに転送することです。これにより、意味のある呼び出しができないという問題が解決さwillChangeValueForKey:
れnil
ます。とはいえ、観測のリストを維持していると仮定すると、KVOがケース#1の場合にここでターゲットを設定することに関連する次のシーケンスを許容することは楽観的ではありません。
- 外部のオブザーバーが呼び出し
-[proxy addObserver:...]
、プロキシが辞書の代理に転送します
-[surrogate willChangeValueForKey:
ターゲットが設定されているため、プロキシ呼び出し]
- 代理呼び出し
-[surrogate removeObserver:...
]代理
-[newTarget addObserver:...]
新しいターゲットでのプロキシ呼び出し
- プロキシコール
-[newTarget didChangeValueForKey:
]コール#2のバランスを取る
これが同じエラーにつながることはないかどうかは私にはわかりません。このアプローチ全体は、本当にホットな混乱になりつつありますね。
私にはいくつかの代替案がありましたが、#1はかなり些細なことであり、#2と#3は十分に単純でも、自信を持って刺激するものでもないので、それらをコーディングするために時間を費やしたくなります。しかし、後世のために、どうですか:
1.NSObjectController
プロキシに使用する
確かに、それはコントローラーを通過するための追加のキーでkeyPathsを強化しますが、これはある種のNSObjectController's
理由ですよね?コンテンツを含めることができnil
、すべての観測のセットアップと破棄を処理します。透過的な呼び出し転送プロキシの目標は達成されませんが、たとえば、非同期で生成されたオブジェクトの代用を目標とする場合、非同期生成操作で最終的なものを配信するのはおそらくかなり簡単です。コントローラへのオブジェクト。これはおそらく最も手間がかからないアプローチですが、実際には「透明な」要件に対応していません。
2.NSObject
プロキシにサブクラスを使用します
NSProxy's
主な機能は、魔法が含まれていることではありません。主な機能は、 (すべての)NSObject
実装が含まれていないことです。NSObject
不要なすべての動作をオーバーライドし、それらを転送メカニズムに戻す努力を惜しまない場合は、NSProxy
KVOサポートメカニズムを残したまま、提供されたものと同じネット値を得ることができます。場所。そこから、プロキシがターゲット上で監視されたものと同じキーパスをすべて監視し、ターゲットからの再ブロードキャストwillChange...
とdidChange...
通知を行って、外部の監視者がそれらをプロキシから送信されたものと見なすようにします。
...そして今、本当にクレイジーな何かのために:
3.(Ab)ランタイムを使用して、NSObject
KVC/KVOの動作をNSProxy
サブクラスに取り込みます
ランタイムを使用して、KVCおよびKVOに関連するメソッド実装を(ie)から取得し、NSObject
それらのメソッド(ie )をプロキシサブクラスにclass_getMethodImplementation([NSObject class], @selector(addObserver:...))
追加できます。class_addMethod([MyProxy class], @selector(addObserver:...), imp, types)
NSObject
これは、パブリックKVOメソッドが呼び出すすべてのプライベート/内部メソッドを把握し、それらを卸売りするメソッドのリストに追加するという推測とチェックのプロセスにつながる可能性があります。KVOの遵守を維持する内部データ構造は、すべてのインスタンスがスペース価格を支払うことを意味するため、のivarsでは維持されないと仮定するのは論理的ですNSObject
( ivarsがないNSObject.h
ことを示します-それは最近何も意味しません) 。NSObject
また、KVO通知のスタックトレースに多くのC関数が含まれています。NSProxyがKVOのファーストクラスの参加者になるのに十分な機能を導入できたと思います。その時点から、このソリューションは次のようになります。NSObject
ベースのソリューション; ターゲットを観察し、通知が自分からのものであるかのように再ブロードキャストし、さらに、ターゲットへの変更に関するwillChange/didChange通知を偽造します。KVOパブリックAPI呼び出しのいずれかを入力するときにフラグを設定し、パブリックAPIのときにフラグをクリアするまで、呼び出されたすべてのメソッドを引き継ぐことで、呼び出し転送メカニズムでこの一部を自動化できる場合もあります。コールリターン-これらのメソッドを引き継ぐことでプロキシの透明性が損なわれないことを保証しようとする問題があります。
これが失敗するのではないかと思うのは、KVOが実行時にクラスの動的サブクラスを作成するメカニズムです。そのメカニズムの詳細は不透明であり、おそらくから持ち込むためのプライベート/内部メソッドを理解するための別の長い列につながるでしょうNSObject
。結局、このアプローチも完全に脆弱であり、内部実装の詳細が変更されないようにします。
...結論は
要約すると、問題は、KVOが、キースペース全体で一貫性があり、知識があり、一貫して更新される(通知を介して)状態を期待しているという事実に要約されます。(バインディングをサポートまたは編集できるようにする場合は、そのリストに「可変」を追加し-setValue:forKey:
ます。)汚いトリックを除いて、ファーストクラスの参加者であることは。であることを意味しNSObjects
ます。チェーン内のこれらのステップの1つが、他の内部状態を呼び出すことによってその機能を実装する場合、それはその特権ですが、KVOコンプライアンスのすべての義務を果たす責任があります。
NSObject
そのため、これらのソリューションのいずれかが努力する価値がある場合は、「プロキシとしてではなく、を使用する」ことにお金をかけると思いますNSProxy
。したがって、質問の正確な性質を理解するために、KVOに準拠したサブクラスを作成する方法があるかもしれませNSProxy
んが、それだけの価値があるとは思えません。