Call any super class’s implementation of any method

In Objective-C, you can only call an overridden implementation of a method from an immediate subclass of that method’s class. This works well to provide encapsulation, and based on the way super is handled, a super method implementation cannot be called from any but the immediate superclass (inheritance notwithstanding). This is a good thing, as it would break large bits of encapsulation if subclasses could call arbitrary super implementations, or worse, if other objects could call super implementations of methods on foreign objects.

What we’re going to do

This encapsulation-breaking design-pattern destroyer will also have a -super method that takes no arguments and messages the immediate superclass, but can be chained together, so that we may use dot-syntax. However, the use of this method is a malfeasance in the world of Objective-C, where the actual class of an object may be a subclass of the class which you have declared it a pointer to an object of, and you will end up calling the implementation of the wrong superclass. Additionally, in the case where you are using it on self, the runtime lookup of the superclass may result in the calling of the implementation from the class of the caller, if self is actually a subclass which happened to be in a call to a super implementation of its own, which is absolutely a Very Bad Thing and will likely lead to a crash.

All in all, by the end of this we will be able to write things like this:

[[Superclass levelSuperOf:self] dosomething];
[[Superclass levelSuperOf:self] performSelector:@selector(dosomething)];
[object.super dosomething];
[object_getSuper(myNonNSObjectInheritingObject, itsClass) dosomething];
[[object superWithClass:[Superclass class]] dosomething];

The implementation

One additional note is that we must implement +(id)superWithClass:(Class)superclass, should never actually be called when one could simply call the method on the superclass directly, if it were at hand. However, the metaclass of NSObject actually inherits from NSObject, which is unbelievably confusing (explained rather well here, especially in the comments), but basically means that -superWithClass: would be available in something like [NSObjectSubclass superWithClass:NSObject], unless you actually 'overload' it with +superWithClass:. At any rate, for my +superWithClass:, I simply return the passed superclass.

For the actual implementation of object_getSuper, we return a HigherOrderMessage object (full code on my github repo, abbreviated here)

id object_getSuper(id object, Class superclass) {return [HigherOrderMessage HOMWithGetSignatureForSelector:^NSMethodSignature *(SEL aSelector) {
return [object methodSignatureForSelector:aSelector];
} capture:^(NSInvocation *message) {
//Snip. Extra code handling performSelector:, its variants, and super
[message invokeWithTarget:object superclass:superclass];

…which hands off the work to a NSInvocation category,

@implementation NSInvocation (SuperMessenger)- (void)invokeWithTarget:(id)target superclass:(Class)superclass {
[SuperMessenger superMessage:self withObject:target class:superclass];

...which hands off the work to a class called SuperMessenger

Some Background

All of the above can be found in Apple's official Objective-C language documentation, especially the first two sections.

Keep in mind that the difference between the signatures of objc_msgSendSuper is the destination type of the first argument. This is so after objc_msgSendSuper has done the method implementation lookup, it can swap the pointer for the actual receiver without any additional fuss. (It cannot however simply look up the superclass of the receiver at runtime, for reasons mentioned above in 'What We're Going To Do'.) Both of these methods lookup the method implementation in the superclasses (sequentially) of the target class if the method is not found in the target class.

The SuperMessenger implementation

The 'forwarding methods' are all exactly the same, however they are separated for the sole purpose of giving them different return types for the runtime. They work by calling the __builtin_apply family of GCC extensions to apply all (even unknown) arguments passed to them to objc_msgSendSuper, but only after swapping the receiver (self) and selector (_cmd) for their proper values residing in ivars target and selector. Additionally, an integer containing the frame length of the method is required for __builtin_apply, but we do not have to worry about this as NSMethodSignature provides a convenient method returning just that, so all we have to do is store it in an ivar in -invoke:. Lastly, once we set self to the objc_super, ivars will become unavailable to us (although the compiler won't give us any errors), so we need to remember to set self after we get the other two ivars or we crash.

The Core Code

#define SUPERMESSAGE(method) {									\
int framelength = frameLength; \
_cmd = selector; \
self = (id)⌖ \
__builtin_return(__builtin_apply((void(*)())method, __builtin_apply_args(), framelength)); \
- (long long)superMessage SUPERMESSAGE(objc_msgSendSuper)
- (double)superMessageFpret SUPERMESSAGE(objc_msgSendSuper)
- (CGRect)superMessageStret SUPERMESSAGE(objc_msgSendSuper_stret)
//CGRect used to simulate generic struct
- (void)invoke:(NSInvocation *)invoc {
NSMethodSignature *methodSignature = invoc.methodSignature;
frameLength = methodSignature.frameLength;
char retType = methodSignature.methodReturnType[0];
selector = invoc.selector;
if (retType == 'd' || retType == 'f') {
invoc.selector = @selector(superMessageFpret);
} else if (methodSignature.methodReturnLength > 8) {
invoc.selector = @selector(superMessageStret);
} else {
invoc.selector = @selector(superMessage);
[invoc invokeWithTarget:self];

Platform/Compiler Dependence - You should never, ever, EVER use this

I may eventually return to this topic, when I have fully delved into the calling conventions of the various platforms on which Objective-C runs, but regardless I dare say it necessary to wrap most/all of the code in 'The Core Code' in a platform-specific ifdef. Upon that continuation, however, I would need to make a choice to either leave the firm foundation of fact and journey into thickets of wildest guesswork with regard to the generated data structure returned by __builtin_apply_args, or write the forwarders in assembly. While assembly really is the (very, very) best tool for the job, there are two problems: I don't have any experience writing assembly, and if we were going to use assembly we should have just used libffi/libffi-iphone (which are both really cool).


So now we can, on Mac OS 32 bit applications, call an arbitrary super implementation of an arbitrary class.

You can download the project here.

The best use of this that I can think of is skipping over specific overridden implementations of a superclass method to call a higher (inheritance-wise) implementation. This could actually be rather useful, if it weren't for the fact that this means you're breaking encapsulation, however I guess it is understandable if used to hack Cocoa.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store