OC Runtime methods and messaging

Posted by tomhath on Thu, 04 Jul 2019 18:36:57 +0200

OC method calls are familiar to us, a simple piece of code, a class named MyObject

#import <Foundation/Foundation.h>

@interface MyObject : NSObject

-(void)printSomeThing:(NSString *)age;
-(void)printSomeThing;

@end
#import "MyObject.h"

@implementation MyObject

-(void)printSomeThing:(NSString *)age {

    NSLog(@"name = %@, age = %@",NSStringFromClass([self class]), age);
}

-(void)printSomeThing{

    NSLog(@"come here");
}

@end

Called this way in a ViewController

#import "ViewController.h"
#import "MyObject.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    MyObject *object = [[MyObject alloc] init];
    [object printSomeThing:@"kobe"];
    [object printSomeThing];

}

Convert to c++ code from xcrun-sdk iphonesimulator clang-rewrite-objc ViewController.m as follows:

Generated ViewController.cpp with the core code we want

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));


    MyObject *object = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("alloc")), sel_registerName("init"));

    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)object, sel_registerName("printSomeThing:"), (NSString *)&__NSConstantStringImpl__var_folders_db_k2cngcsd0g91h3dbfbbgrjdr0000gn_T_ViewController_c46f7e_mi_0);


    ((void (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("printSomeThing"));

}

objc_msgSend

From the code above, we find that method calls are made through objc_msgSend, and the compiler will convert our code to objc_msgSend

id objc_msgSend(id self, SEL op, ...);

self: Object to receive message

op: selector for the method to process the message

...: Variable parameters

Return value: The return value of the method

When objc_msgSend encounters a method call, the compiler chooses to call one of the following methods

Message sending for objc_msgSend normal object

objc_msgSend_stret Message sent when the return value is a data structure

Message sending of objc_msgSendSuper parent class

objc_msgSendSuper_stret Message sent by parent class when return value is data structure

At Apple Open Source It is important to say that:

These functions must be cast to an appropriate function pointer type
before being called.
This method must be forced to the appropriate function pointer type before being called.

If you force a function pointer to another type in C, you will get an undefined behavior error, so in the above method, first convert objc_msgSend to void *
It then reverts to the desired type (void ()(id, SEL, NSString)) because it must be forced to the appropriate function pointer type before being called.The other is (void (**)(id, SEL)).

SEL

Defines an opaque type to represent a method selector


typedef struct objc_selector *SEL;

Method selectors are used to represent method names in runtime, which is a C string registered in OC Runtime. When a class is loaded, the selector generated by the compiler is automatically registered in runtime.

Using sel_registerName, you can add a new selector to the runtime or get an existing runtime from the runtime.Used multiple times in the code above.

When using selector, you must use the return value of sel_registerName or @selector() or NSSelector FromString(), and you cannot convert the C string directly to a SEL.

If the methods are the same in OC, they will have the same selector, and in runtime they will be the same C string.It is not possible to have the same method in the same class, so there is no need to consider how to distinguish.For the same selector in different classes, it's ok ay to distinguish it by using the isa mentioned in the previous article.

IMP

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

The selector is just a method name string, and the real implementation is done by IMP.IMP is to represent the address of a function, essentially a function pointer, which points to the real function implementation process.

So how is SEL associated with IMP?

METHOD

typedef struct objc_method *Method;
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

Method links SEL to IMP so that SEL can find the appropriate method and then implement IMP accordingly.

id method_invoke(id receiver, Method m, ...); //Call the implementation of method
SEL method_getName(Method m);  ObtainmethodName, return aSEL
unsigned int method_getNumberOfArguments(Method m);  Number of return method parameters

More Operational Functions

Two hidden parameters in IMP:

  1. self: The object to receive the message
  2. _cmd: SEL for this IMP

Get method address: methodForSelector

void (*setter)(id, SEL, BOOL);  // Function Pointer
int i;

setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)]; //Assigning a value to a pointer to a setter function
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES); //function call

objc_msgSend dynamic binding process

  1. The corresponding IMP is found based on the SEL, and since different classes may have the same selector, the precise IMP is found through the receiver's class in the IMP process.
  2. Invoke IMP, pass receiver, and parameters into IMP
  3. Return its return value

The diagram above shows the distribution process of a message. When sending a message to an object, it first finds its corresponding class based on the object's isa, then finds the selector to execute in the class's cache. If there is no continuation to its method list, if there is no continuation to its parent class, and if there is no continuation to its root class,If no corresponding method is found in the root class, an error will be reported and the IMP corresponding to selector will be executed if it is found.

Dynamic Method Binding

When we send a message to an object, an error occurs if the corresponding selector for the message does not exist.Throwing an error on such a problem is a fairly sensible solution that will eliminate many bug s.When an object receives an unknown message, it first calls the class method of the class it belongs to+resolveInstanceMethod:(instance method) or+resolveClassMethod:(class method).In this method, we can dynamically bind an IMP to the selector so that the selector has an implementation.

void dynamicMethodIMP(id self, SEL _cmd) {

    NSLog(@"This is a  dynimaic ");
}

+(BOOL)resolveInstanceMethod:(SEL)sel{

    if (sel == @selector(dynimaicSel)) {

        class_addMethod([self class], sel, (IMP) dynamicMethodIMP, "v@:");
        return YES;
    }

    return [super resolveInstanceMethod:sel];
}

dynimaicSel is not implemented, so adding an implementation to it via class - addMethod is equivalent to dynamically adding a Method.

Topics: SDK