问题的关键在于键值观察的核心在于NSObject
, and NSProxy
不继承自NSObject
。我有理由相信任何方法都需要NSProxy
对象保留自己的观察列表(即外部人员希望观察它的内容)。仅此一项就会给您的 NSProxy 实现增加相当大的重量。
观察目标
看起来您已经尝试过让代理的观察者实际观察真实的对象 - 换句话说,如果目标始终被填充,并且您只是将所有调用转发到目标,那么您也将转发addObserver:...
and removeObserver:...
来电。问题在于你一开始就说:
NSProxy 似乎作为替代对象工作得很好
还不存在
为了完整起见,我将描述这种方法的一些核心内容以及为什么它不起作用(至少对于一般情况):
为了使其发挥作用,您的NSProxy
子类必须收集在设置目标之前调用的注册方法的调用,然后在设置目标时将它们传递给目标。当您考虑还必须处理删除时,这很快就会变得棘手;您不想添加随后被删除的观察(因为观察对象可能已被释放)。您可能也不希望跟踪观察的方法保留任何观察者,以免造成意外的保留周期。我看到需要处理以下可能的目标值转变
- 目标是
nil
在初始化时,变为非nil
later
- 目标设定为非
nil
,变成nil
later
- 目标设定为非
nil
,然后更改为另一个非nil
value
- 目标是
nil
(不在 init 上),变成非nil
later
...在情况 #1 中我们立即遇到了问题。如果 KVO 观察者只观察到的话,我们可能就没事了objectValue
(因为它永远是你的代理),但是假设观察者观察到了一个通过你的代理/真实对象的 keyPath,比如说objectValue.status
。这意味着 KVO 机器将调用valueForKey: objectValue
在观察的目标上并取回你的代理,然后它会调用valueForKey: status
在你的代理上并且将会得到nil
后退。当目标变为非nil
,KVO 将认为该值已从其下方更改(即不符合 KVO 要求),并且您将收到引用的错误消息。如果你有办法暂时迫使目标返回nil
for status
,你可以打开该行为,调用-[target willChangeValueForKey: status]
,关闭该行为,然后调用-[target didChangeValueForKey: status]
。无论如何,我们可以在案例 #1 处停止,因为它们有相同的陷阱:
-
nil
如果你打电话,不会做任何事willChangeValueForKey:
其上(即 KVO 机器永远不会知道在转换到或从nil
)
- 迫使任何目标物体具有一种暂时躺下并返回的机制
nil
来自 valueForKey: 对于所有键来说,当所声明的愿望是“透明代理”时,这似乎是一个相当繁重的要求。
- 在带有 a 的代理上调用 setValue:forKey: 是什么意思?
nil
目标?我们是否保留这些价值观?等待真正的目标?我们扔吗?巨大的开放问题。
对此方法的一种可能的修改是当真实目标是时使用替代目标nil
,也许是一个空的NSMutableDictionary
,并将 KVC/KVO 调用转发给代理。这将解决无法有意义地调用的问题willChangeValueForKey:
on nil
。话虽如此,假设您保留了观察列表,我并不乐观地认为 KVO 会容忍以下在情况#1 中设置目标所涉及的序列:
- 外部观察员电话
-[proxy addObserver:...]
,代理转发到字典代理
- 代理调用
-[surrogate willChangeValueForKey:
] 因为目标正在设定
- 代理调用
-[surrogate removeObserver:...
] 关于代理
- 代理调用
-[newTarget addObserver:...]
朝着新的目标
- 代理调用
-[newTarget didChangeValueForKey:
] 平衡调用#2
我不清楚这是否也会导致同样的错误。整个方法真的会变得一团糟,不是吗?
我确实有几个替代想法,但#1 相当微不足道,而#2 和#3 不够简单,也不够鼓舞人心,不足以让我想花时间来编写它们。但是,对于后代来说,怎么样:
1. Use NSObjectController
为您的代理
当然,它会用一个额外的密钥来连接你的 keyPaths 来通过控制器,但这有点像NSObjectController's
存在的全部理由,对吗?它可以有nil
内容,并将处理所有观察设置和拆除。它没有实现透明的调用转发代理的目标,但例如,如果目标是为某些异步生成的对象提供一个替代对象,那么让异步生成操作传递最终的对象可能会相当简单向控制器发出对象。这可能是最省力的方法,但并没有真正满足“透明”的要求。
2. 使用NSObject
您的代理的子类
NSProxy's
主要特征不是它has它有一些魔力——主要特点是它没有(一切NSObject
其中的实施。如果你愿意付出努力去推翻一切NSObject
你的行为don't想要的,并将它们分流回您的转发机制,您最终可以获得与NSProxy
但保留了 KVO 支持机制。从那里开始,您的代理监视目标上观察到的所有相同关键路径,然后重新广播willChange...
and didChange...
来自目标的通知,以便外部观察者将其视为来自您的代理。
...现在是一些非常疯狂的事情:
3. (Ab)使用运行时来带NSObject
KVC/KVO 行为融入您的NSProxy
子类
您可以使用运行时来获取与KVC和KVO相关的方法实现NSObject
(i.e. class_getMethodImplementation([NSObject class], @selector(addObserver:...))
),然后您可以添加这些方法(即class_addMethod([MyProxy class], @selector(addObserver:...), imp, types)
) 到你的代理子类。
这可能会导致猜测和检查过程,找出所有私有/内部方法NSObject
公共 KVO 方法调用,然后将它们添加到您批发的方法列表中。假设维护 KVO 遵守的内部数据结构不会在 ivars 中维护,这似乎是合乎逻辑的NSObject
(NSObject.h
表示没有 ivars——现在这并不意味着什么),因为这意味着每个NSObject
实例将支付空间价格。另外,我在 KVO 通知的堆栈跟踪中看到了很多 C 函数。我认为您可能已经为 NSProxy 引入了足够的功能,成为 KVO 的一流参与者。从那时起,这个解决方案看起来就像NSObject
基于解决方案;您观察目标并重新广播通知,就好像它们来自您一样,此外还围绕目标的任何更改伪造 willChange/didChange 通知。你might甚至可以在您的调用转发机制中自动执行其中的一些操作,方法是在您输入任何 KVO 公共 API 调用时设置一个标志,然后尝试调用您调用的所有方法,直到您在公共 API 调用返回时清除该标志为止- 那里的问题是试图保证引入这些方法不会破坏代理的透明度。
我怀疑这会失败的地方在于 KVO 在运行时创建类的动态子类的机制。该机制的细节是不透明的,并且可能会导致另一长串找出私有/内部方法以从NSObject
。最后,这种方法也是完全脆弱的,以免任何内部实现细节发生变化。
...综上所述
抽象地说,问题归结为这样一个事实:KVO 期望在其密钥空间上有一个连贯的、可知的、持续更新的(通过通知)状态。 (如果您想支持,请将“可变”添加到该列表中-setValue:forKey:
或可编辑的绑定。)除非有肮脏的伎俩,成为一流的参与者意味着NSObjects
。如果链中的这些步骤之一通过调用其他内部状态来实现其功能,这是它的特权,但它将负责履行 KVO 合规性的所有义务。
出于这个原因,我认为如果这些解决方案中的任何一个值得付出努力,我会把钱花在“使用NSObject
作为代理而不是NSProxy
” 因此,为了了解您问题的确切性质,may成为一种方式NSProxy
符合 KVO 的子类,但似乎不值得。