objective-c - NSProxy和键值观察

标签 objective-c cocoa key-value-observing nsproxy

NSProxy似乎可以作为尚不存在的对象的替代对象。例如。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}


上面的代码将透明地将任何方法调用传递给代理表示的目标。但是,它似乎无法处理对目标的KVO观测和通知。我尝试使用NSProxy子类来代表要传递给NSTableView的对象,但是出现以下错误。

Cannot update for observer <NSAutounbinderObservance 0x105889dd0> for
 the key path "objectValue.status" from <NSTableCellView 0x105886a80>,
 most likely because the value for the key "objectValue" has changed
 without an appropriate KVO notification being sent. Check the 
KVO-compliance of the NSTableCellView class.


有没有办法使透明的NSProxy符合KVO?

最佳答案

问题的症结在于键值观察的胆量生活在NSObject中,而NSProxy并非继承自NSObject。我有理由相信,任何方法都将要求NSProxy对象保留其自己的遵守情况列表(即,外部人士希望观察的内容。)仅此一项就可以为您的NSProxy实现增加可观的分量。

遵守目标

看起来您已经尝试让代理的观察者实际观察到真实的对象-换句话说,如果目标始终被填充,并且您只是将所有调用转发给目标,那么您也将转发addObserver:...removeObserver:...通话。问题是您开始说:


  NSProxy似乎可以很好地作为替代对象使用
  尚不存在


为了完整起见,我将介绍这种方法的一些要点以及为什么它行不通(至少对于一般情况而言):

为了使它起作用,您的NSProxy子类将必须收集设置目标之前调用的注册方法的调用,然后在设置目标时将它们传递给目标。当您认为还必须执行删除操作时,这很快就会变得繁琐。您不希望添加随后被删除的观察值(因为观察对象可能已被取消分配)。您也可能不希望使用跟踪观察的方法来保留任何观察者,以免造成意外的保留周期。我看到目标值需要进行以下可能的转换


目标在初始化时为nil,稍后变为非nil
目标设置为非nil,以后变为nil
目标设置为非nil,然后更改为另一个非nil
目标是nil(不在init上),后来变为非nil


...在第一种情况下,我们立即遇到了问题。如果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:不会执行任何操作(即,在过渡到nil或从中过渡出来时,KVO机械永远不会知道更新其内部状态)
强制任何目标对象具有一种机制,该机制将临时说谎并从valueForKey返回nil:对于所有键,当声明的愿望是“透明代理”时,这似乎是一个相当繁重的要求。
在具有nil目标的代理上调用setValue:forKey:甚至意味着什么?我们是否保持这些价值观?等待真正的目标?我们扔吗?巨大的公开问题。


对这种方法的一种可能的修改是,当实际目标为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子类

您可以使用运行时从NSObject(即class_getMethodImplementation([NSObject class], @selector(addObserver:...)))获取与KVC和KVO相关的方法实现,然后可以将这些方法(即class_addMethod([MyProxy class], @selector(addObserver:...), imp, types))添加到代理子类中。

这可能导致猜测和检查过程,找出公共KVO方法调用的NSObject上的所有私有/内部方法,然后将这些方法添加到要批发的方法列表中。逻辑上可以假设,保持KVO遵守的内部数据结构不会以NSObject的值保留(NSObject.h表示没有ivars –如今不代表任何含义),因为那意味着每个NSObject实例将支付空间价格。另外,我在KVO通知的堆栈跟踪中看到很多C函数。我认为您可能已经达到了某种程度,为NSProxy引入了足够的功能使其成为KVO的一流参与者。从那时起,此解决方案看起来像基于NSObject的解决方案;您观察目标并重新广播通知,就好像它们来自您一样,此外,伪造willChange / didChange通知围绕目标的任何更改。您甚至可以在调用转发机制中通过在输入任何KVO公共API调用时设置一个标志,然后尝试继承您所调用的所有方法,直到在清除公共API时清除标志,来自动执行其中的某些操作呼叫返回-可能会试图确保带来这些方法而不会破坏代理的透明性。

我怀疑这种情况会落在KVO在运行时创建类的动态子类的机制中。该机制的细节是不透明的,可能会导致花费很长的时间弄清楚从NSObject引入的私有/内部方法。最后,这种方法也是完全脆弱的,以免任何内部实现细节发生变化。

...结论

概括地说,问题归结为这样一个事实,即KVO期望在其关键空间内保持一致,可知,一致地更新(通过通知)状态。 (如果您想支持-setValue:forKey:或可编辑的绑定,请在该列表中添加“可变”。)禁止肮脏的技巧,成为一流的参与者意味着成为NSObjects。如果链中的这些步骤之一通过调用其他内部状态来实现其功能,那将是其特权,但它将负责履行其对KVO合规性的所有义务。

因此,我认为如果这些解决方案中的任何一项值得付出努力,我都会把钱花在“使用NSObject作为代理而不是NSProxy”上。因此,要弄清您问题的确切性质,也许可以采用一种方法来制作符合KVO的NSProxy子类,但似乎并不值得。

关于objective-c - NSProxy和键值观察,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9054970/

相关文章:

objective-c - 在 Realm 迁移期间,如何进行多对一迁移?

ios - 更聪明的边界检测方案-ios

objective-c - CALayer : Single pixel line looks like 2 pixels

ios - AVPlayer 键值的 currentItem 是否可观察?

ios - 您还没有注册任何设备 token - Urban Airship

iphone - View Controller 添加 subview

macos - 检查 NSTask 是否正在创建文件

ios - 缓存 NSDateformatter 应用程序范围的好主意吗?

ios - 未释放的 KVO 发生奇怪的崩溃

ios - 当我向另一个类中的 NSMutableArray 添加新元素时,如何重新加载 UITableView?