13

我正在尝试通过设置一个 NSProxy 子类来代替我选择的任何 UIView 来向我的 UIViews 添加功能(根据状态配置 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: 的深处,这一系列方法调用显示在日志中(此处显示的第一条消息是通过代理):

- 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 上的 superlayer 被调用而不是 CALayer。也许某处有些东西变得混乱并且 UILabel 被插入到子层而不是它的 CALayer 中?

我错过了什么吗?

UIKit 是否进行了某种绕过 NSProxy 挂钩的正常机制的优化?

其他想法?

谢谢!

亨利

PS我只在模拟器中试过这个,而不是设备。这种行为会有什么不同吗?

4

4 回答 4

4

我试图解决同样的问题 - 当我遇到这个问题时,将 NSProxy 与 UIView(在我的情况下为 UITableViewCell)一起使用。我记录了对控制台的所有调用:

...
App[2857:c07] MyHeaderCell: --- method signature for: _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- _unsubscribeToScrollNotificationsIfNecessary:
App[2857:c07] MyHeaderCell: --- method signature for: _makeSubtreePerformSelector:withObject:
App[2857:c07] MyHeaderCell: --- _makeSubtreePerformSelector:withObject:
App[2857:c07] +[MyHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] CRASH: +[SMSHeaderCell superlayer]: unrecognized selector sent to class 0x1331f8c
App[2857:c07] Stack Trace:...

unrecognized selector它在异常时崩溃。

通常,首先向对象询问- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel方法,当返回时,它会调用- (void) forwardInvocation:(NSInvocation *)invocation代理中的 。这样我们就可以重定向消息。如果没有NSMethodSignature返回,则doesNotRecognizeSelector:在对象上调用该方法。所以我们甚至得到了无法识别的选择器调用。

这适用于实例方法,但这种崩溃是由我们无法控制的类方法引起的 - 对象本身没有被调用(类是)。我想通过覆盖我的 NSProxy 子类的 getter 来强制运行时调用我的代理类,即使是类方法

- (Class) class
{
    return _myRealClass;
}

这没有用。所以 NSProxy 不足以做到这一点。现在我正在尝试使用 NSObject 而不是 NSProxy 来实现所有所需的行为,因为 NSObject 具有+ (BOOL)resolveClassMethod:(SEL)sel可能有用的方法。一旦我发现 NSObject 是否更适合这个,我会编辑这篇文章。

//编辑

似乎问题在于使用 NSProxy,superlayerUIView不是CALayer. 证明:http ://t2523.codeinpro.us/q/508112244f1eba38a4fb032e

所以它看起来真的像一个 UIKit 快捷方式问题 - 他们没有发送常规消息调用(我猜是速度优化)。

无论如何,我正在寻找一种方法来解决这个问题。

于 2013-08-27T11:04:43.450 回答
3

我放弃了尝试。我得出的结论是 NSProxy 是一个未被充分利用的对象,它在 Apple 示例之外的潜在用途尚未得到充分探索或调试。简而言之,我相信 NSProxy 还没有准备好用作扩展对象功能的通用方式,而无需子类化或添加类别。

在过去,我会使用poseAsClass 调用来实现我想要的功能。

我的解决方案最终是这样的:

  • 我向 UIView 添加了一个类别,该类别添加了其他属性。这些属性实现将它们的 set & get 消息转发到 UIView 的“addOn”属性,我也将其放入类别中。UIView 的类别实现中这个“addOn”属性的默认值当然是 nil。(我本可以实现一个静态哈希表来为任何 UIView 关联一个 AddOn 实例,但它让我觉得正确管理保留计数是一种冒险的策略。)

  • “AddOn”类中有额外的代码来直接操作 UIView,并在其中添加了额外的绘图代码。

  • 对于我想要添加此附加功能的每种类型的 UIView,我必须使用以下代码对其进行子类化:a) 为“AddOn”类创建实例方法和相应的属性代码 b) 将我涵盖的任何函数子类化以提供“ AddOn”代码有机会添加它的功能。

  • 这些子类中的每一个都具有基本相同的代码,用于将所需的功能转发到 AddOn 实例。

所以,我最终尽可能地减少了代码重复,但是每个 UIView 的后代子类都可以使用“AddOn”功能最终导致重复代码。

看来我可以通过使用类方法操作函数进一步减少代码重复,但是学习曲线和代码的进一步混淆阻止了我走这条路。

于 2011-04-16T14:28:15.183 回答
2

我从未尝试过将 NSProxy 与视图一起使用,但我通过使用自定义视图类来显示另一个视图做了类似的事情。也许系统需要一个实际的视图而不是一个代理对象。有两种方法可以使用“代理”视图:

  1. 使代理视图成为代理视图的子视图。代理将从代理视图中获取框架、自动调整掩码等,然后将代理视图添加为其子视图并将其框架设置为代理视图的边界,并将其自动调整掩码设置为始终填充代理视图。删除代理视图后,所有设置都会从代理视图复制回其中。任何未复制到代理中的属性都会使用转发传递给代理视图。

  2. 代理视图几乎将每条消息都传递给代理视图。代理视图不会覆盖 lock/unlockFocus、display 等方法。它覆盖 drawRect: 以在代理视图上调用 drawRect:。

于 2011-02-21T01:59:08.187 回答
2

在尝试了同样的事情并搜索了错误(这让我来到这里)之后,我试图规避这些问题......它并不漂亮。

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:

  1. 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 the UIView. Couldn't get far there.

  2. Declare just the CALayer * in the proxy subclass, and only copy that pointer from the protected UIView 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 branch

我还写了一篇关于细节的博文。

于 2014-03-12T05:01:36.560 回答