objective-cinheritancemessage-forwarding

How does Objective-C messaging work? Where is `super`? How to make sure a class has an implementation from its super before method swizzling?


I have some question about Obj-C message.

(1) It's my main question: how to make sure a class has an implementation from its superclass before swizzling it or invoking it?

If I want to do method swizzling with a method which the target class didn't implement it, but its superclass did, how to make sure this class has the implementation from its superclass before method swizzling?

I know when a message sent to an object which did not implement it, the Obj-C runtime will first look up its inherit hierarchy to find the implementation from its superclass. If there is one, Obj-C will cache it in a different dispatch table and invoke it.

Can I do method swizzling with this cached implementation? If can, how to make sure there is an cached implementation before method swizzling or sending any message to this object? If not, how to add a real implementation to this class which will just invoke the super's implementation before method swizzling?

For example, there is a class hierarchy like this:

@interface A : NSObject
- (void) helloWorld;
@end

@interface B : A
@end

@interface C : B
@end

@interface D : C
@end

@interface E : D
@end

and the implementation like this:

@implemenation A
- (void) helloWorld{
    NSLog(@"hello world from class A");
}
@end

@implemenation B
// did not override -helloWorld
@end

@implemenation C
- (void) helloWorld{
    [super helloWorld];
    NSLog(@"hello world from class C");
}
@end

@implemenation D
// did not override -helloWorld
@end

@implemenation E
// did not override -helloWorld
@end

I want to just exactly exchange (not adding) the implementation of -helloWorld with the Class D at runtime. This trick will add an additional task to the original implementation if the swizzled method like this:

@implemenation D (AdditionalWorkForHelloWorld)
- (void) additionalWorkForHelloWorld{
    [self additionalWorkForHelloWorld];
    NSLog(@"hello every one!");
}
@end

So when I send a -helloWorld message to the instance of Class D or Class E, the console will print the following messages:

-> hello world from class A
-> hello world from class C
-> hello every one!

Someday, after the class D implements its -helloWorld as following:

@implemenation D
- (void) helloWorld{
    [super helloWorld];
    NSLog(@"hello world from class D");
}
@end

and send a -helloWorld message to the instance of Class D or Class E, the console will print the following messages:

-> hello world from class A
-> hello world from class C
-> hello world from class D
-> hello every one!

This method exchanging will make sure the additional task will be invoked whether class D implements -helloWorld or not.

I'v asked a question about how to make a C function to invoke an object's super's implementation, and it seems not easy to do that. How does Obj-C achieve this cached mechanism to forward the message to super's implementation?

(2) How dose Obj-C handle super keyword? and where is super?

In the Obj-C message, the first two arguments are hidden: self and _cmd. You can use them in an Obj-C implementation. But where is super?

Is a super equal to the self's isa pointer? How dose Obj-C handle super keyword?

Every Obj-C message will convert to objc_msgSend() or objc_msgSend_stret(). So does every message sent to super will convert to objc_msgSendSuper() and objc_msgSendSuper_stret()?

In the example mentioned previously, when I send a -helloWorld message to the Class E, and Class D and E don't respond it, Obj-C will send the message to Class C's implementation. In the Class C's implementation, it invokes its super's implementation, but Class B did not implement it, so forward it to Class A.

In this case, the super of Class C should be Class B, so it actually invokes Class B's cached implementation, doesn't it? May I swizzle the Class B's cached implementation?


Solution

  • A message to super is sent to self. However, the lookup of the implementation starts at a different place. When you message self, the Objective-C run time (objc_msgSend(), etc.) looks first in the class pointed to by self's isa pointer. If no implementation is found there, the search moves to the superclass, and so on.

    When you message super, the search starts in the superclass of the class whose code contained the invocation of super. Yes, the message to super translates into a call to objc_msgSendSuper() or one of its variants. The compiler constructs an objc_super structure that contains self and the superclass of the class currently being compiled. (Note: that's not the same as [self superclass]. That expression is dynamic and depends on the actual class of self. The superclass targeted by a message to super is not dynamic and doesn't depend on the actual class of self.)

    Anyway, objc_msgSendSuper() uses the superclass referenced in the objc_super structure to control where it starts the search for the implementation, but otherwise behaves like objc_msgSend(). That's it. That's all that super does: it starts the search for the implementation further along the chain of classes.

    As to how to accomplish your swizzling goal… I think you want to first try adding an implementation that calls super. If that fails (because the class already has such a method), then swizzle in a different implementation that calls the original.

    So, something like:

    @implemenation D (AdditionalWorkForHelloWorld)
    - (void) addedHelloWorldIfNotPresent{
        [super helloWorld];
        NSLog(@"hello every one!");
    }
    - (void) additionalWorkForHelloWorld{
        [self additionalWorkForHelloWorld];
        NSLog(@"hello every one!");
    }
    @end
    

    Then try adding the implementation of -addedHelloWorldIfNotPresent using class_addMethod(). Obtain the implementation using +[NSObject instanceMethodForSelector:] with @selector(addedHelloWorldIfNotPresent). If adding the method fails, then swizzle in -additionalWorkForHelloWorld for the existing -helloWorld, instead.