I've seen a few SO questions similar to this one, but they all involve singletons, and the answers are all (correctly) "don't do that, use dispatch_once() instead."
In my particular instance, I'm not making a singleton, and I need for the superclass's allocWithZone:
to return an instance of my subclass.
What I'm trying to do is: I want to make an extension to an existing library that I don't own. Rather than doing a bunch of behaviours in categories -- which won't even work properly if I'm overriding existing behaviour, because which-of-many same-signature methods gets called isn't defined -- I want it to be that, when someone includes my (very small) category, then calls
LibObject *myObj = [[LibObject alloc] init];
They get back, instead, as if they'd called
LibObject *myObj = [[MyLibObjSubclass alloc] init];
where LibrarySubclass
is the subclass which I'm creating. In particular, All of the library's objects that are subclasses of LibObject
, I want them, instead, to be subclasses of MyLibObjSubclass
.
I've messed with swizzling and categories and everything else I can think of, but I always end up with one of 2 problems: either
[MyLibObjSubclass alloc]
calling [LibObject alloc]
calling [MyLibObjSubclass alloc]
, etc., orLibObject
, rather than being a subclass of MyLibObjSubclass
, with its new behaviours, it just is a MyLibObjSubclass
, without any of the additional behaviours.For this 2nd problem, by way of example, let's say I was trying to override all UIView
-s to be MyView
-s, where MyView
is a subclass of UIView
. The problem is that, when I try to category-in (as part of UIView+MyShim
), then this line:
UILabel *label = [[UILabel alloc] init];
instead of returning a UILabel
that is a subclass of MyView -> UIView -> UIResponder -> NSObject, it just returns a MyView
which, for example, doesn't contain a text
property, so all UILabel
-s are broken.
So my question is: how do I do what I want?
Thanks!
It sounds like you want to modify the class hierarchy from this:
CCResponder
└─ CCNode
├─ CCControl
└─ CCSprite
to this:
CCResponder
└─ MyResponder
└─ CCNode
├─ CCControl
└─ CCSprite
Since class_setSuperclass
is deprecated, there's no supported way to make that change at runtime.
(NOTE: Yes, that's exactly what I was hoping to do. ~Olie.)
Have you considered just modifying the Cocos2D source code? Short-term, this should be the simplest approach. If what you need is general enough, maybe you can get your changes merged upstream. Or maybe you can add hooks that will be accepted upstream, and use those hooks to do what you need.
If you're keeping your code in git, then I recommend using the git-subtree extension to import the Cocos2D source code into your project's repository. This makes it pretty easy to keep track of your local changes to Cocos2D and reapply them when you want to import a newer version.
If that doesn't work for you, I think you should just suck it up and use swizzling on the methods whose behaviors you want to change.
If you are a glutton for punishment, you can create a new subclass of each CCResponder
subclass, on demand at runtime. You'll end up with a hierarchy like this:
CCResponder
└─ CCNode
├─ CCControl
│ └─ hacked_CCControl
└─ CCSprite
└─ hacked_CCSprite
(I'm aware that CCControl
isn't something you're likely to instantiate directly but let's pretend.)
I think this is possible, but pretty complicated. To do it, swizzle +[CCResponder alloc]
with (your own) +[CCResponder allocHack]
. You would implement something like this:
static Class createHackedClassForClass(Class originalClass) {
NSString *name = [@"hacked_" stringByAppendingString:NSStringFromClass(originalClass)];
Class hackedClass = objc_allocateClassPair(originalClass, name.UTF8String, 0);
// Here add methods as necessary to hackedClass using class_addMethod.
objc_registerClassPair(hackedClass);
return hackedClass;
}
+ (void)allocHack {
static NSMutableDictionary *hackedClassForClass = [NSMutableDictionary dictionary];
Class hackedClass = hackedClassForClass[self];
if (hackedClass == nil) {
hackedClass = createHackedClassForClass(self);
hackedClassForClass[self] = hackedClass;
}
return class_createInstance(hackedClass, 0);
}
I haven't tried this so I don't know if there's any major problems with it. It will at a minimum make things harder to debug than just modifying the Cocos2D source code.