NSProxy和关键价值观察

Ton*_*ony 14 cocoa objective-c key-value-observing nsproxy

NSProxy对于那些尚不存在的对象来说,它们看起来效果非常好.例如.

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

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
Run Code Online (Sandbox Code Playgroud)

上面的代码将透明地将任何方法调用传递给代理所代表的目标.但是,它似乎没有处理目标上的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.
Run Code Online (Sandbox Code Playgroud)

有没有办法让透明NSProxy符合KVO标准?

ipm*_*mcc 20

问题的关键在于,关键价值观察的内涵存在于NSObject并且NSProxy不会继承NSObject.我有理由相信任何方法都需要NSProxy对象保留自己的观察列表(即外界人员希望观察它的内容.)仅此一项就会给你的NSProxy实现增加相当大的分量.

观察目标

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

NSProxy似乎非常适合那些尚不存在的替代物

为了完整起见,我将描述这种方法的一些内容以及为什么它不起作用(至少对于一般情况):

为了使其工作,您的NSProxy子类必须收集在设置目标之前调用的注册方法的调用,然后在设置目标时将它们传递给目标.当你考虑到你还必须处理清除时,这很快变得毛茸茸; 你不会想要添加一个随后被移除的观察(因为观察对象可能已经被释放).您也可能不希望跟踪观察的方法保留任何观察者,以免造成意外的保留周期.我看到了目标值中需要处理的以下可能的转换

  1. 目标nil在初始化,变得不nil
  2. 目标被设置为非nil,变得更nil
  3. 目标设置为非nil,然后更改为另一个非nil
  4. 目标是nil(不是在初始阶段),变得不nil

......我们立即遇到问题#1.如果KVO观察者只观察到objectValue(因为那将永远是你的代理人),我们可能会在这里,但是说观察者已经观察到一个通过你的代理/真实对象的keyPath objectValue.status.这意味着KVO机器将调用valueForKey: objectValue观察目标并返回您的代理,然后它将调用valueForKey: status您的代理并将nil返回.当目标变为非目标时nil,KVO会认为该值已从其下方更改(即不符合KVO)并且您将收到所引用的错误消息.如果你有办法暂时强制目标返回nilstatus,你可以打开,呼叫行为-[target willChangeValueForKey: status],把行为关闭,然后调用-[target didChangeValueForKey: status].无论如何,我们可以在#1的情况下停在这里,因为他们有相同的陷阱:

  1. nil如果你打电话willChangeValueForKey:,它将不会做任何事情(即KVO机器永远不会知道在过渡期间更新其内部状态nil)
  2. 强制任何目标对象都有一个机制,它会暂时躺下并nil从valueForKey 返回:因为当所述的欲望是"透明代理"时,所有键看起来都是一个非常繁重的要求.
  3. 在具有nil目标的代理上调用setValue:forKey:甚至意味着什么?我们保持这些价值吗?等待真正的目标?我们扔了吗?巨大的未决问题.

对这种方法的一种可能的修改是当真实目标nil可能是空的时,使用替代目标NSMutableDictionary,并且向代理人进行前向KVC/KVO调用.这将解决无法有意义地呼叫的willChangeValueForKey:问题nil.所有这一切,假设你已经保留了你的观察列表,我不乐观KVO会容忍在#1中设置目标所涉及的以下序列:

  1. 外部观察者呼叫-[proxy addObserver:...],代理转发到字典代理
  2. 代理调用-[surrogate willChangeValueForKey:]因为正在设置目标
  3. 代理要求-[surrogate removeObserver:...对替代]
  4. 代理呼叫-[newTarget addObserver:...]新目标
  5. 代理呼叫-[newTarget didChangeValueForKey:]以平衡呼叫#2

我不清楚这不会导致同样的错误.整个方法真的变成了一团糟,不是吗?

我确实有一些替代想法,但#1相当简单,#2和#3不够简单或充满信心,足以让我想要花时间来编写代码.但是,对后代来说,怎么样:

1. NSObjectController用于您的代理

当然,它通过一个额外的键来控制你的keyPaths,但这就是存在的NSObjectController's全部原因,对吧?它可以有nil内容,并将处理所有观察设置和拆除.它没有实现透明的调用转发代理的目标,但是,例如,如果目标是为某些异步生成的对象设置一个替代,那么让异步生成操作交付最终可能是相当简单的.对象到控制器.这可能是最省力的方法,但并没有真正解决"透明"的要求.

2.使用NSObject代理的子类

NSProxy's主要特征并不是它一些神奇之处 - 主要特征是它没有(全部)NSObject实现.如果您愿意努力覆盖NSObject想要的所有行为,并将它们分流到您的转发机制中,您最终可以得到相同的净值,NSProxy但保留了KVO支持机制地点.从那里开始,你的代理就会看到目标上观察到的所有相同的关键路径,然后从目标转发willChange...didChange...通知,以便外部观察者看到它们来自你的代理.

......现在真的很疯狂:

3.(Ab)使用运行时将NSObjectKVC/KVO行为引入NSProxy子类

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

这可能会导致猜测和检查过程,找出NSObject公共KVO方法调用的所有私有/内部方法,然后将这些方法添加到您批发的方法列表中.似乎合乎逻辑的假设维持KVO遵守的内部数据结构不会被维持在ivars NSObject(NSObject.h表示没有ivars - 这并不意味着任何东西),因为这意味着每个NSObject实例都会支付空间价格.此外,我在KVO通知的堆栈跟踪中看到了很多C函数.我认为你可能已经达到了这样的程度,你已经为NSProxy带来了足够的功能,成为KVO的一流参与者.从那时起,该解决方案看起来像NSObject基于解决方案; 你观察目标并重新传播通知,就好像它们来自你一样,另外假装围绕对目标的任何变化进行更改/更改变更通知.你可能甚至可以通过设置一个标志在您的调用转发机制来自动一些这方面,当你进入任何一个国际志愿者组织的公共API调用,然后尝试,直到您清除标志带过来都叫你方法时,公共API调用返回 - 那将试图保证带来这些方法不会破坏代理的透明度.

我怀疑这将落在KVO在运行时创建类的动态子类的机制中.该机制的细节是不透明的,可能会导致另一长串的私人/内部方法从中引入NSObject.最后,这种方法也非常脆弱,以免任何内部实现细节发生变化.

...结论

在摘要中,问题归结为这样一个事实:KVO期望在其关键空间内实现连贯,可知,持续更新(通过通知)状态.(如果你想支持-setValue:forKey:或编辑绑定,可以在该列表中添加"mutable" .)除了肮脏的技巧,作为一流的参与者意味着存在NSObjects.如果链中的其中一个步骤通过调用其他内部状态来实现其功能,那就是它的特权,但它将负责履行其对KVO合规性的所有义务.

出于这个原因,我认为如果这些解决方案中的任何一个都值得付出努力,那么我就把钱花在"使用NSObject代理而非代理NSProxy"上.因此,为了找到问题的确切性质,可能有一种方法可以使NSProxy符合KVO 的子类,但似乎不值得.