NSProxy seems to work very well as stand-in objects for those that don't yet exist. For example.
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
The above code will transparently pass any method invocation to the target that the proxy represents. However, it doesn't seem to handle KVO observations and notifications on the target. I tried to use a NSProxy subclass as standing for objects to be passed to NSTableView, but I'm getting the following error.
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.
Is there a way to make transparent NSProxy that is KVO compliant?
The crux of the issue is that the guts of Key-Value Observing lives in
NSObject, andNSProxydoesn't inherit fromNSObject. I'm reasonably confident that any approach will require theNSProxyobject to keep its own list of observances (i.e. what outside folks are hoping to observe about it.) This alone would add considerable weight to your NSProxy implementation.Observe the target
It looks like you've already tried having observers of the proxy actually observe the real object -- in other words, if the target were always populated, and you simply forwarded all invocations to the target, you would also be forwarding
addObserver:...andremoveObserver:...calls. The problem with this is that you started out by saying:For completeness, I'll describe some of the guts of this approach and why it can't work (at least for the general case):
In order for this to work, your
NSProxysubclass would have to collect invocations of the registration methods that were called before the target was set, and then pass them through to the target when it gets set. This quickly gets hairy when you consider that you must also process removals; you wouldn't want to add an observation that was subsequently removed (since the observing object could have been dealloc'ed). You also probably don't want your method of tracking observations to retain any of the observers, lest this create unintended retain cycles. I see the following possible transitions in target value that would need to be handlednilon init, becomes non-nillaternil, becomesnillaternil, then changes to another non-nilvaluenil(not on init), becomes non-nillater...and we run into problems right away in case #1. We would probably be all right here if the KVO observer only observed
objectValue(since that will always be your proxy), but say an observer has observed a keyPath that goes through your proxy/real-object, sayobjectValue.status. This means that the KVO machinery will have calledvalueForKey: objectValueon the target of the observation and gotten your proxy back, then it will callvalueForKey: statuson your proxy and will have gottennilback. When the target becomes non-nil, KVO will have considered that value to have changed out from under it (i.e. not KVO compliant) and you'll get that error message you quoted. If you had a way to temporarily force the target to returnnilforstatus, you could turn that behavior on, call-[target willChangeValueForKey: status], turn the behavior off, then call-[target didChangeValueForKey: status]. Anyway, we can stop here at case #1 because they have the same pitfalls:nilwon't do anything if you callwillChangeValueForKey:on it (i.e. the KVO machinery will never know to update its internal state during a transition to or fromnil)nilfrom valueForKey: for all keys seems like a pretty onerous requirement, when the stated desire was a "transparent proxy".niltarget? do we keep those values around? waiting for the real target? do we throw? Huge open issue.One possible modification to this approach would be to use a surrogate target when the real target is
nil, perhaps an emptyNSMutableDictionary, and forward KVC/KVO invocations to the surrogate. This would solve the problem of not being able to meaningfully callwillChangeValueForKey:onnil. All that said, assuming you've maintained your list of observations, I'm not optimistic that KVO will tolerate the following sequence that would be involved with setting the target here in case #1:-[proxy addObserver:...], proxy forwards to dictionary surrogate-[surrogate willChangeValueForKey:] because target is being set-[surrogate removeObserver:...] on surrogate-[newTarget addObserver:...]on new target-[newTarget didChangeValueForKey:] to balance call #2It's not clear to me that this won't also lead to the same error. This whole approach is really shaping up to be a hot mess, isn't it?
I did have a couple alternate ideas, but #1 is fairly trivial and #2 and #3 aren't simple enough or confidence-inspiring enough to make me want to burn the time to code them up. But, for posterity, how about:
1. Use
NSObjectControllerfor your proxySure, it gums up your keyPaths with an extra key to get through the controller, but this is sort of
NSObjectController'swhole reason for being, right? It can havenilcontent, and will handle all the observation set up and tear-down. It doesn't achieve the goal of a transparent, invocation forwarding proxy, but for example, if the goal is to have a stand-in for some asynchronously generated object, it would probably be fairly straightforward to have the asynchronous generation operation deliver the final object to the controller. This is probably the lowest-effort approach, but doesn't really address the 'transparent' requirement.2. Use an
NSObjectsubclass for your proxyNSProxy'sprimary feature isn't that it has some magic in it -- the primary feature is that it doesn't have (all) theNSObjectimplementation in it. If you're willing to go to the effort to override allNSObjectbehaviors that you don't want, and shunt them back around into your forwarding mechanism, you can end up with the same net value provided byNSProxybut with the KVO support mechanism left in place. From there, it's a matter of your proxy watching all the same key paths on the target that were observed on it, and then rebroadcastingwillChange...anddidChange...notifications from the target so that outside observers see them as coming from your proxy....and now for something really crazy:
3. (Ab)Use the runtime to bring the
NSObjectKVC/KVO behavior into yourNSProxysubclassYou can use the runtime to get the method implementations related to KVC and KVO from
NSObject(i.e.class_getMethodImplementation([NSObject class], @selector(addObserver:...))), and then you can add those methods (i.e.class_addMethod([MyProxy class], @selector(addObserver:...), imp, types)) to your proxy subclass.This will likely lead to a guess-and-check process of figuring out all the private/internal methods on
NSObjectthat the public KVO methods call, and then adding those to the list of methods that you wholesale over. It seems logical to assume that the internal data structures that maintain KVO observances would not be maintained in ivars ofNSObject(NSObject.hindicates no ivars -- not that that means anything these days) since that would mean that everyNSObjectinstance would pay the space price. Also, I see a lot of C functions in stack traces of KVO notifications. I think you could probably get to a point where you had brought in enough functionality for the NSProxy to be a first-class participant in KVO. From that point forward, this solution looks like theNSObjectbased solution; you observe the target and rebroadcast the notifications as if they came from you, additionally faking up willChange/didChange notifications around any changes to the target. You might even be able to automate some of this in your invocation forwarding mechanism by setting a flag when you enter any of the KVO public API calls, and then attempting to bring over all methods called on you until you clear the flag when the public API call returns -- the hitch there would be trying to guarantee that bringing over those methods didn't otherwise ruin the transparency of your proxy.Where I suspect this will fall down is in the mechanism whereby KVO creates dynamic subclasses of your class at runtime. The details of that mechanism are opaque, and would probably lead to another long train of figuring out private/internal methods to bring in from
NSObject. In the end, this approach is also completely fragile, lest any of the internal implementation details change....in conclusion
In the abstract, the problem boils down to the fact that KVO expects a coherent, knowable, consistently updated (via notifications) state across it's key space. (Add "mutable" to that list if you want to support
-setValue:forKey:or editable bindings.) Barring dirty tricks, being first class participants means beingNSObjects. If one of those steps in the chain implements it's functionality by calling through to some other internal state, that's its prerogative, but it'll be responsible for fulfilling all its obligations for KVO compliance.For that reason, I posit that if any of these solutions are worth the effort, I'd put my money on the "using an
NSObjectas the proxy and notNSProxy." So to get to the exact nature of your question, there may be a way to make anNSProxysubclass that is KVO compliant, but it hardly seems like it would worth it.