【问题标题】:Objective-C - Is there any way to use any class (UIView) as a Protocol on another object?Objective-C - 有没有办法使用任何类(UIView)作为另一个对象的协议?
【发布时间】:2014-07-31 06:25:20
【问题描述】:

我一直在用一些相当奇特的概念在 Objective-C 中进行试验。

下面是一个例子: 本质上,我有一个名为“theFilter”的对象,它应该从另一个对象“theSender”获取所有消息以及“Some_More_Messages”。 Filter 的实例旨在对任意数量的实例进行过滤,所有实例都属于不同的类。根据某些标准,它以“Some_More_Messages”协议将消息发送给其他对象,而所有其他对象仅采用标准路径。

Filter<Some_More_Messages> *theFilter;

例如,我想在 UIView 实例前面有这个过滤器实例,我需要我的“过滤器”对象能够接收 UIView 实现的所有消息。比如:

Filter<UIView,Some_More_Messages> *theFilter;

但我不能继续为我需要过滤的每个类创建协议。这是为了让编译器停止抱怨并获得自动完成和其他一切。

有什么想法吗?

PS。在“theSender”的代码中,我从不直接引用 UIView 实例,而是与它的“filterInstance”对话并将其发送给 UIView 实例所知道的“Some_More_Messages”。它知道它执行的东西,所有其他选择器都被重定向到其他实例。

【问题讨论】:

  • 听起来您在谈论代理对象,如果是这样,您想阅读运行时指南中的Message Forwarding。另见NSProxy

标签: objective-c objective-c-runtime


【解决方案1】:

你可以实现forwardingTargetForSelector:

@interface Filter : NSObject /*NSProxy*/ <FilterProtocol>

@property UIView *view;

+ (UIView<FilterProtocol> *)filterWithView:(UIView *)view;

@end

@implementation

+ (UIView<FilterProtocol> *)filterWithView:(UIView *)view {
    return (UIView<FilterProtocol> *)[[self alloc] initWithView:view];
}

- (id)initWithView:(UIView *)view {
    self = [super init];
    if (self) {
        self.view = view;
    }
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([self.view respondsToSelector:aSelector]) {
        return self.view;
    }

    return [super forwardingTargetForSelector:aSelector];
}

- (void)someFilterProtocolMethod {}

@end

 UIView<FilterProtocol> *filteredView = [Filter filterWithView:view];
 [filteredView addSubview:otherView]; // call addSubview on view
 [filteredView someFilterProtocolMethod]; // call someFilterProtocolMethod on filter object

【讨论】:

    【解决方案2】:

    更新:这是一个好主意,但实施不当,请检查 Bryan Cheng's answer 以了解正确实施。


    我认为您正在尝试实现多重继承。虽然 Objective-C 不支持这一点,但您可以通过拥有 UIView 的实例来模仿它,并将未处理的消息转发给它。

    您必须将 NSInvocations 转发到 UIView,如下所示:

    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        if ([self respondsToSelector:[anInvocation selector]]) {
            // Try to use our version of the selector
            [super forwardInvocation:anInvocation];
        } else {
            // But if we don't have that method, delegate it to the UIView
            [anInvocation invokeWithTarget:self.view];
        }
    }
    

    您还可以通过覆盖respondsToSelector:isKindOfClass: 和其他一些方法来欺骗其他对象,使其看起来就像您实际上是一个 UIView。在Runtime Programming Guide

    中阅读有关此高级技术的更多信息

    顺便说一句,如果您只是不想让它崩溃,但不需要将消息实际转发到内部UIView,那么如果UIView 方法是respondsToSelector: 方法,则使respondsToSelector: 方法返回YES,但不要在forwardInvocation: 位上做任何事情。

    【讨论】:

    • 第一次检查总是假的,你还必须覆盖 methodSignatureForSelector: 才能让它工作
    • forwardInvocation: 中什么也不做是非常糟糕的。它需要为调用对象设置一个有效的返回值或抛出异常来指示返回值是不可能的。
    • 我不得不承认我自己从来不需要这个,我只是想为他指明正确的方向。所以我只是链接了你的答案,并为你投票;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    • 2013-06-08
    相关资源
    最近更新 更多