What is the runtime

Runtime is an API written in C, C++, and assembly that provides the runtime for OC.

Runtime: loads memory and provides runtime functionality (runtime dependent) Compile-time: compiles high-level languages (OC, Swift, Java, etc.) source code into recognized languages (machine language –> binary)

Underlying library relationship:

The nature of objects and methods

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p = [[LGPerson alloc] init];
        [p run];
    }
    return 0;
}
Copy the code

Clang compile, CD to the corresponding file, open terminal, enter the following command

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o testMain.c++
或
clang -rewrite-objc main.m -o test.c++
Copy the code

Open the generated testmain. c++ file, which is very long with tens of thousands of lines of code, and look at the main ones, as follows

It can be seen that the essence of an object is a structure, and the essence of a method is to send messages. Any method call can be translated as a call to the objc_msgSend method

Class methods and instance methods

The object

LGStudent *s = [[LGStudent alloc] init];
objc_msgSend(s, sel_registerName("run"));
Copy the code

Class method call

objc_msgSend(objc_getClass("LGStudent"), sel_registerName("run"));
Copy the code

Sending a message to a parent class (object method)

struct objc_super mySuper;
mySuper.receiver = s;
mySuper.super_class = class_getSuperclass([s class]);
objc_msgSendSuper(&mySuper, @selector(run));
Copy the code

Send a message to the parent class via objc_msgSendSuper with the structure pointer as the first argument.

Sending a message to a parent class (class method)

struct objc_super myClassSuper; myClassSuper.receiver = [s class]; // Current class myclasssuper. super_class = class_getSuperclass(object_getClass([s class])); // Class of the current class = metaclass objc_msgSendSuper(&myClassSuper, @selector(run));Copy the code

The Runtime can be called in three ways: Runtime API –> (class_, objC_, object_) NSObject API –> (isSelector, isMemberOfClass)

Note: Where do object methods exist? ==> Class instance methods where do class methods exist? In what form does a class method exist in a metaclass? ==> instance method

Message sending Objc_msgSend

Two ways:

  • Fast cache find – through assembly
  • slow

Objc_msgSend is written in a pool, efficient and C language can not be changed by writing a function, leaving unknown parameters, to jump to arbitrary Pointers, assembly can be implemented using registers.

Below enter the dry goods, source code to see how to find IMP, assembly part:

_objc_msgSend is used to determine whether the receiver recevier is null. If the receiver recevier is null, isa is returned. If the receiver is null, ISA is returned. There are three more cachehelookup results. If one is found, a CacheHit call or return IMP is performed. If it is the second kind of CheckMiss, the next function is called __objc_msgSend_uncached; The third is that if the IMP is found somewhere else, add it here for the next quick lookup.

Take a look at calls to __objc_msgSend_uncached:

__class_lookupMethodAndLoadCache3

As can be seen from the above code, this is a long search process, first from their own method list to find, if found, call, at the same time the IMP stored in the cache; If you don’t find it, you look in your parent class, and then you go back and forth, recursively looking in your parent class, until you find NSObject.

The dynamic analysis

The triedResolver variable makes dynamic resolution work only once. Focus on the _class_resolveMethod method:

The above code determines whether it is a metaclass. Instead of using the _class_resolveInstanceMethod method, the metaclass uses the _class_resolveClassMethod method.

+resolveClassMethod +resolveInstanceMethod +resolveClassMethod +resolveInstanceMethod

Let’s look at dynamic parsing in code:

@interface LGPerson : NSObject
- (void)run;
@end

@implementation LGPerson

#pragma mark - Dynamic method parsing
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"Here we go, buddy.");
   return [super resolveInstanceMethod:sel];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [[LGPerson alloc] run];
    }
    return 0;
}
Copy the code

Note that the LGPerson class does not implement the run instance method, nor does it implement the parent class or class. The resolveInstanceMethod: method is overridden in the.m file. Run the code

+ (BOOL)resolveInstanceMethod:(SEL)sel
triedResolver

SEL + (BOOL)resolveInstanceMethod (SEL) SEL add a breakpoint

_objc_msgSend_uncached
lookUpImpOrForward
_class_resolveInstanceMethod
imp

Let’s skip to the + (BOOL)resolveInstanceMethod (SEL) SEL method for the second time

If we are redirecting at this step, we can use the following method

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if(sel == @selector(run)) {// Dynamically parse our object method NSLog(@)"Object method parsing goes here.");
        SEL readSEL = @selector(readBook);                          
        Method readM= class_getInstanceMethod(self, readSEL);
        IMP readImp = method_getImplementation(readM); // Get imp const char * for redirection methodstype = method_getTypeEncoding(readM);
        return class_addMethod(self, sel, readImp, type); // Add method implementation}return [super resolveInstanceMethod:sel];
}
Copy the code

_class_resolveInstanceMethod () : _class_resolveInstanceMethod () : _class_resolveInstanceMethod Call _class_resolveInstanceMethod (resolveInstancemethod, resolveInstancemethod, resolveclassmethod, resolveclassmethod, resolveclassmethod)

Remember where class methods are stored? First, it is a class method of a class, and second, it is an instance method of a metaclass. So in the process of looking for class method IMP is more than a step, if in doubt, can be verified by the following code

ip
ip

// NSObject's classification verifies this problem by commenting out instance methods and class methods#import "NSObject+ZB.h"
#import <objc/runtime.h>

@implementation NSObject (ZB)
+ (void)run {
    NSLog(@"NSObject === + run");
}
- (void)run {
    NSLog(@"NSObject === - run"); } @zbPerson: NSObject + (void)run;} @zbPerson: NSObject + (void)run; Run int main(int argc, const char * argv[]) {@autoreleasepool {[ZBPerson run]; }return 0;
}

Copy the code

According to the above description, the compilation results are as follows

run
Class methods are stored in metaclasses as instance methods

What if I open the comment for the class method run? So let’s see what happens

run
imp

Remember the following diagram to clarify the position of ISA and superclass. (If there are any errors in the diagram, please also point out, thanks)

forward

When dynamic parsing doesn’t get the IMP we want, it returns NO and then goes to message forwarding.

The following shows the use of three methods in message forwarding

#pragma Mark - Message forwarding
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if(aSelector == @selector(run)) {// Forward to our ZBStudent objectreturn [ZBStudent new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s",__func__);
    if(aSelector = = @ the selector (run)) {/ / forwardingTargetForSelector only Method signature without implementation Method Method = class_getInstanceMethod(object_getClass(self), @selector(readBook));
        const char *type = method_getTypeEncoding(method);
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
    NSLog(@"-- -- -- -- -- - % @ -- -- -- -- --",anInvocation);
    anInvocation.selector = @selector(readBook);
    [anInvocation invoke];
}
Copy the code

These three methods, I believe you already very familiar with, method forwardingTargetForSelector: allows us to replace the message receiver for other objects, if this method returns nil or self, will send object methodSignatureForSelector: news, The method’s signature is used to generate the NSInvocation object, which is then translated into the forwardInvocation:, otherwise the object will be returned to resend the message.

The above is the complete message forwarding with the following figure

Many applications are implemented in this layer, but we will not discuss this now, we will focus on how these three methods come from, so continue to look at our source code

_objc_msgForward_impcache

How do you know that all three methods are called during message forwarding?

Introduced a method instrumentObjcMessageSends

extern void instrumentObjcMessageSends(BOOL);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        instrumentObjcMessageSends(YES);
        [ZBPerson  run];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}
Copy the code

InstrumentObjcMessageSends way is to print the current call a method invocation process, compile path can be in after the completion of the Macintosh HD/private/TMP/msgSends – XXXXX msgSends – XXXXX, view the files below