简体   繁体   English

Objective-C - 可可类簇的有效子类化

[英]Objective-C - Effective Subclassing of Cocoa Class Clusters

I have an object that used to be an NSMutableSet but needed some more stuff attached to it. 我有一个对象,曾经是一个NSMutableSet,但需要更多的东西附加到它。 The obvious (and obviously not supported) thing to do is to subclass NSMutableSet and tack on the two additional properties. 显而易见(显然不受支持)的事情是将NSMutableSet子类化并添加两个额外的属性。 Since NSMutableSet, like basically all Cocoa data structures, is a class cluster I cannot subclass it in the usual way, since the super class just throws exceptions. 由于NSMutableSet与基本上所有的Cocoa数据结构一样,是一个类集群,我不能以通常的方式对它进行子类化,因为超类只抛出异常。 This led me down several paths. 这导致我走了几条路。

The first path was to create sort of a composite object that declared itself as a subclass of NSMutableSet but really just forwarded the invocations to an internal NSMutableSet. 第一个路径是创建一个复合对象,它声明自己是NSMutableSet的子类,但实际上只是将调用转发到内部NSMutableSet。 I didn't want to have to implement every method on NSMutableSet, so I thought forwardInvocation: would be a good way to accomplish my mission. 我不想在NSMutableSet上实现每个方法,所以我认为forwardInvocation:将是完成我的任务的好方法。 Unfortunately, the abstract class of NSMutableSet implements all of the methods on the interface and their implementations throw exceptions, so I was never getting to the point where I could forward an invocation. 不幸的是,NSMutableSet的抽象类实现了接口上的所有方法,并且它们的实现抛出异常,所以我从来没有达到可以转发调用的程度。

The second path was to subclass NSProxy and forward the invocation from there. 第二条路径是NSProxy的子类,并从那里转发调用。 This solution falls short in that I need to copy the interface of NSMutableSet over unless there's a way to declare "this class implements this interface" that I don't know about (this could very well be the solution). 这个解决方案不足以说我需要复制NSMutableSet的接口,除非有办法声明“这个类实现这个接口”,我不知道(这很可能是解决方案)。

The third path was to create a category on NSMutableSet and import it just for the class that needs to use it but that falls short since you cannot add non-dynamic properties via a category. 第三条路径是在NSMutableSet上创建一个类别,并仅为需要使用它的类导入它,但由于您无法通过类别添加非动态属性,因此该类别不足。 That led me to using associated objects in a category. 这导致我在一个类别中使用关联对象。 I'm willing to admit that that is the correct solution for this use case, but I wish it weren't since it's kind of clunky. 我愿意承认这是这个用例的正确解决方案,但我希望它不是因为它有点笨重。 It's doubly clunky since the properties I'm adding are primitive so I'll have to wrap and unwrap them when setting and getting the association (unless there's a way to associate primitives which I'm unfamiliar with). 它是双重笨重的,因为我添加的属性是原始的,所以我必须在设置和获取关联时包装和解包它们(除非有一种方法来关联我不熟悉的基元)。

Essentially, what I would like is something that behaves functionally as a subclass of NSMutableSet (and all class clusters) but cannot figure out the best approach. 本质上,我想要的是在功能上作为NSMutableSet(和所有类集群)的子类行为,但无法找出最佳方法。 Thanks! 谢谢!

Trying to subclass Cocoa class clusters will just create an awful lot of hurt. 试图继承Cocoa类集群只会造成很大的伤害。 It may seem a good idea, but you will forever run into problems. 这似乎是一个好主意,但你会永远遇到问题。

Just create an NSObject with an NSMutableSet as the first member object. 只需使用NSMutableSet作为第一个成员对象创建一个NSObject。

Subclassing Cocoa class cluster is kind of discouraged. 对Cocoa类集群进行子类化是有点气馁的。 Not without reasons. 不无道理。 Please do not enter this crashy world. 请不要进入这个崩溃的世界。

Either of your solutions will work. 您的任何一种解决方案都可行。 I've successfully used the first path with NSArray and NSDictionary , so I believe it should work fine for NSMutableSet as well. 我已成功使用NSArrayNSDictionary的第一个路径,所以我相信它也适用于NSMutableSet Just remember that you need to override not only forwardInvocation: , but a few of other methods as well. 请记住,您不仅需要覆盖forwardInvocation:还需要覆盖其他一些方法。 Please consult Surrogate Objects sections of Apple docs: 请参阅Apple文档的Surrogate Objects部分:

Although forwarding mimics inheritance, the NSObject class never confuses the two. 虽然转发模仿继承,但NSObject类从不混淆这两者。 Methods like respondsToSelector: and isKindOfClass: look only at the inheritance hierarchy, never at the forwarding chain. 像respondsToSelector:和isKindOfClass这样的方法:只查看继承层次结构,而不是转发链。

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html

In my case, I've overridden: 就我而言,我已经覆盖了:

  • conformsToProtocol:
  • isKindOfClass:
  • isMemberOfClass:
  • respondsToSelector:
  • instancesRespondToSelector:
  • forwardInvocation:
  • methodSignatureForSelector:
  • instanceMethodSignatureForSelector:

from which isKindOfClass: , conformsToProtocol: and respondsToSelector: are definitely crucial. isKindOfClass: conformsToProtocol:respondsToSelector:肯定是至关重要的。

I've also used the third path with good results, but I admit the associated objects API is clunky. 我也使用了第三条路径并取得了良好的效果,但我承认相关的对象API很笨拙。

First, gnasher729 is correct. 首先,gnasher729是正确的。 Don't subclass class clusters. 不要对类集群进行子类化。 Just don't do it. 只是不要这样做。 Can you do it? 你可以做到吗? If I tell you that you can't, will it help you convince yourself that you shouldn't? 如果我告诉你你做不到,那会不会让你说服你自己不应该? I can lie if it helps you make good choices. 如果能帮助你做出正确的选择,我可以撒谎。

But in all seriousness, it is almost always meaningless as well. 但严肃地说,它几乎总是毫无意义。 Is your subclass really a specific kind of set? 是您真正的子类特定类型的一套? Or is it really kind of like a set. 或者它真的有点一套。 Consider NSAttributedString . 考虑NSAttributedString It isn't a kind of string, it has-a string. 它不是一种字符串,它有一个字符串。 This is almost always better. 这几乎总是更好。

And also, class clusters happen to be a royal pain to subclass. 而且,类集群恰好是子类的王室痛苦。

That said, adding associated values onto a data structure, as you've already discovered, is generally just fine, because what you really want is "hey, I have some data that needs to go along with this other data." 也就是说,正如您已经发现的那样,将相关值添加到数据结构中通常很好,因为您真正想要的是“嘿,我有一些数据需要与其他数据一起使用”。 Wrapping has gotten so easy that it shouldn't really slow you down. 包装变得如此简单,以至于它不应该让你失望。 See https://stackoverflow.com/a/14918158/97337 : 请参阅https://stackoverflow.com/a/14918158/97337

objc_setAssociatedObject(self, animatingKey, @(value), OBJC_ASSOCIATION_RETAIN_NONATOMIC);

And with "one weird trick" , you can make this really easy: 通过“一个奇怪的技巧” ,你可以很容易地做到这一点:

@interface NSObject (BoolVal)
@property (nonatomic, readwrite, assign) BOOL boolVal;
@end

@implementation NSObject (BoolVal)

- (BOOL)boolVal {
    return [objc_getAssociatedObject(self, _cmd) boolValue];
}

- (void)setBoolVal:(BOOL)value {
    objc_setAssociatedObject(self, @selector(boolVal), @(value), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

But I'd still come back to the question of whether this is really a kind of set (rather than just like a set), and whether it really needs to respond to every message that can be sent to a set. 但我还是会回来的,这是否真的是一种集(而不是就像一组)的问题,以及是否真的需要每一个可以发送给一组消息作出响应。 As with NSAttributedString , your real needs are often much smaller than that in practice, and wrapping the handful of methods you need is often worth the simplicity and control. NSAttributedString ,您的实际需求通常比实际需求小得多,并且包含您需要的少数方法通常值得简单和控制。

For completeness, let's look at your first path: 为了完整起见,让我们看看你的第一条道路:

create sort of a composite object that declared itself as a subclass of NSMutableSet but really just forwarded the invocations to an internal NSMutableSet 创建一个复合对象的类,它声明自己是NSMutableSet的子类,但实际上只是将调用转发到内部NSMutableSet

Can you subclass an NSMutableSet ? 你可以子类化NSMutableSet吗? Yes, but should you? 是的,但是你呢? The documentation for NSMutableSet says: NSMutableSet的文档说:

Subclassing Notes 子类注释

There should be little need of subclassing. 应该没有必要进行子类化。 If you need to customize behavior, it is often better to consider composition instead of subclassing. 如果您需要自定义行为,通常最好考虑组合而不是子类化。

So weigh that up and if you want to subclass refer again to the documentation: 所以权衡一下,如果你想要继承,请再次参考文档:

Methods to Override 覆盖的方法

In a subclass, you must override both of its primitive methods: 在子类中,您必须覆盖它们的两个基本方法:

addObject:

removeObject:

You must also override the primitive methods of the NSSet class. 您还必须覆盖NSSet类的基本方法。

And looking at the NSSet class documentation we find its primitive methods are: 看一下NSSet类文档,我们发现它的原始方法是:

Methods to Override 覆盖的方法

In a subclass, you must override all of its primitive methods: 在子类中,您必须覆盖其所有基本方法:

count

member:

objectEnumerator

That's it, 5 methods. 就是这样,5种方法。

You can define your own class as a subclass of NSMutableSet , add an instance variable which is an instance of NSMutableSet , implement 5 methods and redirect them to the set instance, add whatever init methods you wish, and then add your additional properties. 您可以将自己的类定义为NSMutableSet的子类,添加一个实例变量,它是NSMutableSet一个实例,实现5个方法并将它们重定向到set实例,添加您希望的任何init方法,然后添加其他属性。

If performance is of concern then the tradeoff is between redirecting those five methods and accessing associated objects for your additional properties. 如果性能受到关注,则需要权衡重定向这五种方法和访问相关对象以获取其他属性。 You'll need to profile to work that out, but if and only if performance becomes an issue. 您需要进行配置才能解决这个问题,但是当且仅当性能成为问题时。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM