I have a facade singleton that I’d like to forward some class method calls to some “static” class.
At first glance, forwardInvocation: appeared to be a possible solution, however, NSInvocation‘s invokeWithTarget: and setTarget: only accept an id, i. e. a pointer to an instance, not a class itself. I tried handing over [MyTargetClass class] as the target, but I still get a “No known class method […]” error when I call [Facade someForwardedMethod] somewhere. When I call [[Facade sharedInstance] someForwardedMethod] I get a “No visible @interface […] declares the selector […]” error.
Of course I am aware that I also need to override respondsToSelector: and methodSignatureForSelector:, so my code looks like this:
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector]) {
return YES;
} else {
return [MyTargetClass respondsToSelector:aSelector];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [MyTargetClass methodSignatureForSelector:selector];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL aSelector = [anInvocation selector];
if ([MyTargetClass respondsToSelector: aSelector]) {
[anInvocation invokeWithTarget:[MyTargetClass class]];
} else {
[super forwardInvocation:anInvocation];
}
}
Is there a way to make this work, or do I have to choose another approach?
EDIT: I have tried both ways that Rob Napier mentions in his answer, here are my findings:
I can call a class method in my target class through the facade instance with
[(id)[Facade sharedInstance] doSomethingClassyInTargetClass];
It’s a bit uglier than I hoped for, but it works. However, I can not call a class method in my target class when I address the facade class. To quieten the compiler I can write
[(Class)[Facade class] doSomethingClassyInTargetClass];
but then it throws an ‘NSInvalidArgumentException’ “[Facade doSomethingClassyInTargetClass]: unrecognized selector sent to class […]” at runtime. Apparently the class methods of the facade class get resolved without respect for forwardInvocation:, but after all it has a - in front…
Ah, when you added the “No visible @interface […] declares the selector […]”, it is all clear.
Consider a simpler form with just instance methods. You implement
-forwardInvocation:, and then you have code like this:Now,
MyObjectdoesn’t actually claimdoSomethingin its header, even though it will respond to it. So the compiler complains that this probably won’t work. The fix for that is:When you declare something
id, the compiler stops checking whether the target actually implements the selector. But it’s also possible that no interface declaresdoSomething. Then the compiler is once again suspicious that it’s probably a typo, and gives you a warning. And by “no interface,” I mean “no interface that the compiler has access to.” The compiler only has access to interfaces in this compile unit (i.e. the .m file and the headers it includes). So you need to make sure you include the header that defines this selector.Now for classes, you can’t use
id, but you should be able to useClassto achieve the same thing, such as:If may want to see the RNObserverManager sample code from iOS:PTL Chapter 4 which demonstrates something similar.
You should also definitely look at
forwardTargetForSelector:that Ken Thomases references.You don’t have to cast
sharedInstancetoid. Just change the signature, since you’ll always want to use it that way: