I am experimenting in adding functionality to my UIViews (configuring CALayers according to state) by setting up a NSProxy subclass to stand in for any UIView I choose. Here's what I've tried:
In my NSProxy subclass, I have the following code:
#pragma mark Initialization / Dealloc
- (id)initWithView:(UIView *)view
{
delegate = view;
[delegate retain];
return self;
}
- (void)dealloc
{
[delegate release];
[super dealloc];
}
#pragma mark Proxy Methods
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:delegate];
[anInvocation invoke];
return;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
return [delegate methodSignatureForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL rv = NO;
if ([delegate respondsToSelector:aSelector]) { rv = YES; }
return rv;
}
And, using my NSProxy subclass this way:
UILabel *label = [[HFMultiStateProxy alloc] initWithView:[[[UILabel alloc] initWithFrame:cellFrame] autorelease]];
label.text = text;
label.font = font;
label.textAlignment = UITextAlignmentCenter;
label.backgroundColor = [UIColor clearColor];
label.opaque = NO;
[self addSubview:label];
Seems to work until I hit the addSubview: line.
Turning message tracing on ( instrumentObjcMessageSends(YES); ) shows the forwarding for each of the previous messages working until deep inside of the addSubview:, where this series of method calls show up in the log (the first message shown here was invoked via the proxy):
- UILabel UIView _makeSubtreePerformSelector:withObject:
- UILabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- NSMethodSignature NSMethodSignature methodReturnType
- NSMethodSignature NSMethodSignature _argInfo:
- NSMethodSignature NSMethodSignature _frameDescriptor
+ UILabel NSObject resolveInstanceMethod:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject forwardingTargetForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject methodSignatureForSelector:
- UILabel NSObject class
- UILabel NSObject doesNotRecognizeSelector:
And I get the following error:
2011-02-20 16:38:52.048 FlashClass_dbg[22035:207] -[UILabel superlayer]: unrecognized selector sent to instance 0x757d470
if I do not use an NSProxy subclass and instead use a UILabel subclass (HFMultiStateLabel), it works fine. Here is the message trace that occurs once addSubview: is called (HFNoteNameControl is the superview of the label):
- HFNoteNameControl UIView addSubview:
- HFNoteNameControl UIView _addSubview:positioned:relativeTo:
- HFMultiStateLabel UIView superview
- HFMultiStateLabel UIView window
- HFNoteNameControl NSObject isKindOfClass:
- HFNoteNameControl NSObject class
- HFNoteNameControl UIView window
- UIWindow NSObject isKindOfClass:
- UIWindow NSObject class
- HFNoteNameControl UIView _shouldTryPromoteDescendantToFirstResponder
- HFMultiStateLabel UIView _isAncestorOfFirstResponder
- HFMultiStateLabel UIView _willMoveToWindow:withAncestorView:
- HFMultiStateLabel UIView _willMoveToWindow:
- HFMultiStateLabel UIView willMoveToWindow:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- HFMultiStateLabel UIView willMoveToSuperview:
- HFMultiStateLabel UIView _unsubscribeToScrollNotificationsIfNecessary:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:
- HFMultiStateLabel UIView _makeSubtreePerformSelector:withObject:withObject:copySublayers:
- CALayer CALayer sublayers
- CALayer CALayer superlayer
I can verify that each of the methods up until -superlayer are called successfully when using NSProxy. For some reason, with the NSProxy, superlayer on UILabel is being called instead of CALayer. Perhaps somewhere something gets confused and UILabel is inserted into the sublayers instead of its CALayer?
Does the UIKit do some sort of optimizations that bypass the normal mechanism that NSProxy hooks into?
PS I have only tried this in the Simulator, not the device. Would that behavior be any different?
I gave up trying. I've come to the conclusion that NSProxy is such an underused object that it's potential for uses beyond Apple examples has not been fully explored nor debugged. In short, I believe that NSProxy is not ready to be used as a generic way to extend an object's functionality without subclassing or adding a category.
In the old days, I would have used a poseAsClass call to implement my desired functionality.
My solution ended up something like this:
I added a category to UIView that added additional properties. These property implementations forwarded their set & get messages to a "addOn" property of the UIView that I also put into the category. The default value of this "addOn" property in the UIView's category implementation is, of course, nil. (I could have implemented a static hash table to enable associating an AddOn instance for any UIView, but it struck me as a risky ploy to manage with the retain counts properly.)
The "AddOn" class had extra code in it to directly manipulate the UIView, and it added extra drawing code in it.
For each type of UIView that I wanted to add this added functionality, I had to subclass it with code that: a) Created an instance method and corresponding property code for the "AddOn" class b) Subclassed any functions I covered to give the "AddOn" code a chance to add its functionality.
Each of these subclasses has essentially the same code in it to forward the desired functionality to the AddOn instance.
SO, I ended up minimizing code duplication as much as I could, but each of the UIView's descendant subclasses that enable use of the the "AddOn" functionality ends up duplicating code.
It appears that I could have further minimized code duplication by using class method manipulation functions, but that learning curve and further obfuscation of the code deterred me from following that path.