我正在尝试通过设置 NSProxy 子类来代替我选择的任何 UIView,向我的 UIView 添加功能(根据状态配置 CALayers)。这是我尝试过的:
在我的 NSProxy 子类中,我有以下代码:
#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;
}
并且,以这种方式使用我的 NSProxy 子类:
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];
似乎一直有效,直到我点击 addSubview: 行。
打开消息跟踪 (instrumentObjcMessageSends(YES);) 显示了之前每条消息的转发,直到 addSubview: 内部深处为止,这一系列方法调用显示在日志中(此处显示的第一条消息是通过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:
我收到以下错误:
2011-02-20 16:38:52.048 FlashClass_dbg[22035:207] -[UILabel superlayer]: unrecognized selector sent to instance 0x757d470
如果我不使用 NSProxy 子类而是使用 UILabel 子类(HFMultiStateLabel),它工作正常。以下是调用 addSubview: 后发生的消息跟踪(HFNoteNameControl 是标签的超级视图):
- 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
我可以验证使用 NSProxy 时是否成功调用了 -superlayer 之前的每个方法。由于某种原因,使用 NSProxy 时,将调用 UILabel 上的超级层而不是 CALayer。也许某个地方有些混乱,UILabel 被插入到子层而不是 CALayer 中?
UIKit 是否会进行某种绕过 NSProxy 挂钩的正常机制的优化?
PS我只在模拟器中尝试过这个,没有在设备中尝试过。这种行为会有什么不同吗?
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?
发布评论
评论(4)
在尝试了同样的事情并搜索错误(这让我来到这里)之后,我试图规避这些问题......这并不漂亮。
找出根本问题很容易。在框架的某个地方,Apple 使用直接指针访问 UIView 子类中的变量。如果检查标头,就会发现变量是使用
@package
访问标识符声明的。我基本上尝试的是:
使用从
UIView
类定义复制的ivars在运行时创建一个代理类,然后将这些指针的值设置为UIView
中的对象代码>.无法到达那里。仅在代理子类中声明
CALayer *
,并且仅从受保护的UIView
实例复制该指针。有效,但我认为它有问题?不过,它根本不适用于自动布局,因此我决定放弃该解决方案。我尝试的代码可以在代理下的
RTLSegmentedControl
存储库 中找到-pattern分支我还写了一篇博文有关详细信息。
After trying the same thing, and searched for the error (which got me here), I tried to circumvent the problems... It wasn't pretty.
Identifying the root problem was easy. Somewhere in the framework, Apple is using direct pointer access to the variables in
UIView
subclasses. If you check the headers, the variables are declared with@package
access identifier.What I basically tried was:
Create a proxy class at runtime with ivars copied from the
UIView
class definition, and then set the values of these pointers to the objects in theUIView
. Couldn't get far there.Declare just the
CALayer *
in the proxy subclass, and only copy that pointer from the protectedUIView
instance. Worked, but I think it was buggy? It didn't work with auto layout at all, though, so I decided to move away from that solution.The code I tried can be found in the
RTLSegmentedControl
repo under the proxy-pattern branchI also wrote a blog post about the details.
我从未尝试过将 NSProxy 与视图一起使用,但我通过使用自定义视图类来显示另一个视图来完成类似的操作。也许系统需要一个实际的视图而不是代理对象。有两种方法可以使用“代理”视图:
使代理视图成为代理视图的子视图。代理将从代理视图中获取框架、自动调整大小掩码等,然后将代理视图添加为其子视图,并将其框架设置为代理视图的边界及其自动调整大小掩码,以便它始终填充代理视图。删除代理视图后,所有设置都会从代理视图复制回其中。任何未复制到代理中的属性都会使用转发传递到代理视图。
代理视图将几乎所有消息传递到代理视图。代理视图不会覆盖 lock/unlockFocus、display 等方法。它重写了drawRect:以在代理视图上调用drawRect:。
I have never tried using NSProxy with views, but I have done something similar by using a custom view class to display another view. Maybe the system requires an actual view and not a proxy object. There are two ways you could use a "proxy" view:
Make the proxied view a subview of the proxy view. The proxy would take the frame, autoresizing mask, etc. from the proxied view, then add the proxied view as its subview and set its frame to be the proxy view's bounds, and its autoresizing mask so that it always fills the proxy view. When the proxied view is removed, any settings are copied back into it from the proxy view. Any properties not copied into the proxy are passed to the proxied view using forwarding.
The proxy view passes almost every message to the proxied view. The proxy view does not override the lock/unlockFocus, display, etc. methods. It overrides drawRect: to call drawRect: on the proxied view.
当我遇到这个问题时,我试图解决同样的问题 - 将 NSProxy 与 UIView (在我的例子中为 UITableViewCell )一起使用。我记录了对控制台的所有调用:
它因
无法识别的选择器
异常而崩溃。通常,首先会向对象询问
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
方法,当该方法返回时,它会调用- (void)forwardInitation:(NSInitation *)inluck
在代理中。这样我们就可以重定向消息。如果没有返回NSMethodSignature
,则在对象上调用doesNotRecognizeSelector:
方法。所以我们甚至会收到无法识别的选择器调用。这适用于实例方法,但此崩溃是由类方法引起的,我们无权控制该方法 - 对象本身未被调用(类被调用)。我想通过覆盖 NSProxy 子类的 getter 来强制运行时调用我的代理类,即使是类方法,
但这不起作用。所以 NSProxy 不足以做到这一点。现在我正在尝试使用 NSObject 而不是 NSProxy 来实现所有所需的行为,因为 NSObject 有
+ (BOOL)resolveClassMethod:(SEL)sel
方法,这可能很有用。一旦我发现 NSObject 是否更适合这个,我将编辑这篇文章。//编辑
问题似乎是使用 NSProxy 时,在
UIView
而不是CALayer
上调用superlayer
。所以这看起来确实是一个 UIKit 快捷方式问题 - 他们没有发送常规消息调用(我猜是速度优化)。
无论如何,我现在正在寻找一种方法来解决这个问题。
I was trying to solve the same issue - use NSProxy with UIView (in my case UITableViewCell) when I encountered this problem. I logged all calls to the console:
It crashes on the
unrecognized selector
exception.Normally, the object is asked the
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
method first and when that is returned, it invokes the- (void) forwardInvocation:(NSInvocation *)invocation
in the proxy. This way we can redirect the messages. If there is notNSMethodSignature
returned, thedoesNotRecognizeSelector:
method is called on the object. So we get even unrecognized selector calls.This works for instance methods, but this crash is caused by a class method, which we have no power over - the object itself is not called (the class is). I wanted to force the runtime to call my proxy class even for class methods by overriding the getter of my NSProxy subclass
Which did not work. So NSProxy is not enough to do this. Right now I'm trying to use the NSObject instead of NSProxy to achieve all the desired behavior and since NSObject has the
+ (BOOL)resolveClassMethod:(SEL)sel
method which might be useful. I will edit this post once I found out if NSObject is better suited for this.//Edit
It seems that the problem is that with NSProxy,
superlayer
is being called onUIView
instead ofCALayer
.So it really seems like a UIKit shortcut problem - they're not sending a regular message call (speed optimization I would guess).
Anyways, this I am searching for a way to get around this now.
我放弃了尝试。我得出的结论是 NSProxy 是一个未被充分利用的对象,它在 Apple 示例之外的用途尚未得到充分探索或调试。简而言之,我认为 NSProxy 还没有准备好用作扩展对象功能的通用方法,而无需子类化或添加类别。
在过去,我会使用poseAsClass调用来实现我想要的功能。
我的解决方案最终是这样的:
我向 UIView 添加了一个类别,该类别添加了其他属性。这些属性实现转发了它们的集合和属性。获取消息到 UIView 的“addOn”属性,我也将其放入该类别中。当然,UIView 类别实现中这个“addOn”属性的默认值是 nil。 (我本可以实现一个静态哈希表来为任何 UIView 关联一个 AddOn 实例,但我认为正确管理保留计数是一种危险的策略。)
“AddOn”类中有额外的代码直接操作UIView,并在其中添加了额外的绘制代码。
对于我想要添加此附加功能的每种类型的 UIView,我必须使用以下代码对其进行子类化:
a) 为“AddOn”类创建实例方法和相应的属性代码
b) 对我介绍的所有函数进行子类化,以便为“AddOn”代码提供添加其功能的机会。
这些子类中的每一个都具有基本相同的代码,用于将所需的功能转发到 AddOn 实例。
所以,我最终尽可能地减少了代码重复,但是每个允许使用“AddOn”功能的 UIView 后代子类最终都会重复代码。
看来我可以通过使用类方法操作函数来进一步减少代码重复,但是学习曲线和代码的进一步混淆阻止了我走这条路。
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.