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
, and NSProxy
doesn't inherit from NSObject
. I'm reasonably confident that any approach will require the NSProxy
object 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.
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:...
and removeObserver:...
calls. The problem with this is that you started out by saying:
NSProxy seems to work very well as stand-in objects for those that don't yet exist
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 NSProxy
subclass 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 handled
nil
on init, becomes non-nil
laternil
, becomes nil
laternil
, then changes to another non-nil
valuenil
(not on init), becomes non-nil
later...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, say objectValue.status
. This means that the KVO machinery will have called valueForKey: objectValue
on the target of the observation and gotten your proxy back, then it will call valueForKey: status
on your proxy and will have gotten nil
back. 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 return nil
for status
, 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:
nil
won't do anything if you call willChangeValueForKey:
on it (i.e. the KVO machinery will never know to update its internal state during a transition to or from nil
)nil
from valueForKey: for all keys seems like a pretty onerous requirement, when the stated desire was a "transparent proxy".nil
target? 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 empty NSMutableDictionary
, and forward KVC/KVO invocations to the surrogate. This would solve the problem of not being able to meaningfully call willChangeValueForKey:
on nil
. 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:
NSObjectController
for your proxySure, it gums up your keyPaths with an extra key to get through the controller, but this is sort of NSObjectController's
whole reason for being, right? It can have nil
content, 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.
NSObject
subclass for your proxyNSProxy's
primary feature isn't that it has some magic in it -- the primary feature is that it doesn't have (all) the NSObject
implementation in it. If you're willing to go to the effort to override all NSObject
behaviors that you don't want, and shunt them back around into your forwarding mechanism, you can end up with the same net value provided by NSProxy
but 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 rebroadcasting willChange...
and didChange...
notifications from the target so that outside observers see them as coming from your proxy.
...and now for something really crazy:
NSObject
KVC/KVO behavior into your NSProxy
subclassYou 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 NSObject
that 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 of NSObject
(NSObject.h
indicates no ivars -- not that that means anything these days) since that would mean that every NSObject
instance 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 the NSObject
based 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 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 being NSObjects
. 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 NSObject
as the proxy and not NSProxy
." So to get to the exact nature of your question, there may be a way to make an NSProxy
subclass that is KVO compliant, but it hardly seems like it would worth it.