0

Situation

For a project of mine, I'm building some kind of extension. This extension must have a class that implements a method whose declaration is - (id)initWithBundle:(NSBundle *)bundle.

Issue

My extension has multiple classes, but the host app is so badly written that it calls - (id)initWithBundle:(NSBundle *)bundle on different classes, randomly.

I'm not willing to reduce the number of classes, so the only solution left would be to somehow forward to caller to the class that actually implement - (id)initWithBundle:(NSBundle *)bundle (A bit like a HTTP 302). I found many resources on forwarding calls, but not such thing as forwarding an -init method...

Perceval
  • 247
  • 2
  • 14
  • 1
    What problem have you had using the standard forwarding techniques on init? Init is not very magical; it's just a method. Of course this whole line of work is likely to be insanely fragile and highly unrecommended. It is rare that this kind of workaround turns out to be better than fixing or rewriting the offending code in the long run (or even medium run). That said; it's your app. – Rob Napier Aug 07 '15 at 21:15
  • @RobNapier being who you are, I totally believe what you say about it being fragile... Unfortunately, the main app isn't my code, but Apple's, and it's source code isn't public :-( – Perceval Aug 07 '15 at 21:25
  • What Apple code is this? Are you sure you've understood Apple's code correctly? It seems unlikely that they're calling `initWithBundle:` randomly. Are you a preference pane? – Rob Napier Aug 07 '15 at 21:30
  • I'm a NSMenuExtra. So, private and undocumented stuff. I guess I'm getting what I deserve. – Perceval Aug 07 '15 at 21:33
  • If you have a reasonably small example that shows this behavior, I'd be very interested. I've never seen this behavior (and I assume an `NSStatusItem` is insufficient to your needs; those are of course actually supported, unlike extras). – Rob Napier Aug 07 '15 at 21:36
  • (It has been a few releases since I've built a menu extra, and I know that Apple has broken a lot of unsupported things in the last few releases. But I know that menu extras like iStat Menus still work fine.) – Rob Napier Aug 07 '15 at 21:40
  • The “some kind of extension” isn't perchance a .prefPane and the “the host app” isn't perchance System Preferences.app / legacyLoader.xpc, are they? – Slipp D. Thompson Dec 22 '22 at 07:38

1 Answers1

1

init is allowed to return an object other than itself. While I highly recommend fixing the calling code (I can't imagine a case where allowing for code that calls anything "randomly" is even a reasonable idea), if you want to return some other object from init, it works like this:

- (id)initWithBundle:(NSBundle *)bundle {
    // I don't actually implement this, let's return the class that does
    return [[OtherClass alloc] initWithBundle: bundle];
}

ARC will deal with throwing you away.

The caller now has the wrong type of course (they expect your type, and they have some other random object), which is very likely to lead to hijinks, very hard to track bugs, and general sorrow. But it's legal ObjC. And if the two classes have enough overlap of their methods, it might even work.

In normal cases, this pattern is called a Class Cluster. See What exactly is a so called "Class Cluster" in Objective-C? for some examples.

Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I could do it that way, but then I'd have to reimplement *proxy* methods in every class for every original method. Isn't there something that could just forward everything. Or perhaps I could swizzle the object's ISA ? – Perceval Aug 07 '15 at 21:32
  • I don't understand. Do you want methods to go to the originally requested object or some other object? If the originally requested objected, just implement `initWithBundle:` there (though it's not clear why it would be called), and forward to `init`. If some other object, my code above returns that other object. It will respond to all the messages it responds to. Perhaps a fuller example of your problem? – Rob Napier Aug 07 '15 at 21:38
  • Yes, you're completely right. I just shouldn't code this late. Unfortunately, I can't test you solution because it seems to be in a streak of I'm-working-at-the-wrong-time. Thanks again, I guess it'll do the trick. – Perceval Aug 07 '15 at 21:41
  • I think an init method is always supposed to call `[super init...]`, even if it eventually returns some other object. – newacct Aug 08 '15 at 08:27
  • "In normal cases, this pattern is called a Class Cluster." But in all Cocoa's class clusters, `[[X alloc] init...]` *will* always return an instance of `X` -- it might not be exactly `X`, but it will be `X` or some subtype thereof, so assigning the result to `X *` is appropriate. – newacct Aug 08 '15 at 08:29