This post was first published on my personal blog “Uninhibited Pavilion”

Article link: bujige.net/blog/iOS-Ru…


This article introduces the basics of “Runtime” in iOS development. In this article you will learn:

  1. What is Runtime?
  2. The fundamentals of the messaging mechanism
  3. Concept parsing in Runtime
  4. Runtime Message Forwarding
  5. Summary of message sending and forwarding mechanism

1. What is Runtime?

As we all know, there are usually three steps to turn source code into an executable program: compile, link, and run. The operations performed in these three steps are somewhat different for different compilation languages.

C language, as a static class language, has determined the data types of all variables in the compilation stage, and also determined the function to be called, as well as the implementation of the function.

Objective-c is a dynamic language. At compile time, the exact data type of the variable is not known, nor is it known which function is actually called. The data type of a variable is checked only at run time, and the specific function to call is looked up at run time based on the function name. So we don’t know exactly what happens when we call a method when the program isn’t running.

Objective-c’s mechanism for postponing critical work from compilation and linking to runtime makes objective-C more flexible. We can even change the implementation of a method dynamically while the program is running, which opens up the possibility of the popular “hot update”.

The Runtime is the foundation of everything that implements the Objective-C Runtime mechanism.

Runtime is a library that allows you to dynamically create objects, examine objects, and modify classes and object methods while the program is running.


2. Basic principles of the message mechanism

In Objective-C, object method calls are like [Receiver Selector]; Is essentially the process of having an object send a message at run time.

Let’s look at method calls [Receiver Selector]; What is done at compile time and at run time?

  1. Compilation stage:[receiver selector];Methods are converted by the compiler to:
    1. Objc_msgSend (receiver, the selector)(Without parameters)
    2. Objc_msgSend (recevier, selector, org1, org2...)(with parameters)
  2. Runtime phase: message receiverreceverLook for the correspondingselector.
    1. throughrecevierIsa pointerfindrecevierClass (Class);
    2. inClass (Class)Cache (method cache)In the hash table ofIMP (Method implementation);
    3. If theCache (method cache)Is not found inIMP (Method implementation)If so, continue inClass (Class)Method listTo find the correspondingselectorIf found, fill toCache (method cache)And returnselector;
    4. If theClass (Class)I didn’t find this inselector, just continue in itsSuperClass (superClass)Looking for;
    5. Once we find a matchselector, direct executionreceverThe correspondingselectorMethod implementedIMP (Method implementation).
    6. If you can’t find a matchselector, the message is forwarded or temporarily directed toreceverAdd theselectorThe corresponding implementation method, otherwise a crash will occur.

There are several new concepts involved in the above process: objc_msgSend, ISA pointer, Class, IMP, etc. Let’s explain the meaning of each concept in detail.


3. Concept analysis in Runtime

3.1 objc_msgSend

All Objective-C method calls are converted at compile time to calls to the C function objc_msgSend. Objc_msgSend (receiver, the selector); Is the [receiver selector]; The corresponding C function.

3.2 Class (Class)

In objc/ Runtime.h, Class is defined as a pointer to the objc_class structure, which has the following data structure:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa;                                          // The instance pointer to the objc_class structure

#if ! __OBJC2__
    Class _Nullable super_class;                                 // A pointer to the parent class
    const char * _Nonnull name;                                  // Class name
    long version;                                                // Version information of the class, default is 0
    long info;                                                   // Class information, some bit identifiers for run-time use
    long instance_size;                                          // The size of the instance variable of the class;
    struct objc_ivar_list * _Nullable ivars;                     // List of instance variables for the class
    struct objc_method_list * _Nullable * _Nullable methodLists; // List of method definitions
    struct objc_cache * _Nonnull cache;                          // Method cache
    struct objc_protocol_list * _Nullable protocols;             // List of protocols to comply with
#endif

};
Copy the code

As you can see, the objC_class structure defines many variables: all of its instance variables (ivars), all of its method definitions (methodLists), a list of protocols to comply with (protocols), and so on. The data stored in the objC_class structure is called metadata.

The isa pointer holds a pointer to an instance of the objc_class structure, which is called an object. In other words, a Class is essentially an object, which we call a Class object.

3.3 Objects

Next, let’s look at the definition of Object in objc/objc.h. Object is defined as an objc_object structure, which has the following data structure:

/// Represents an instance of a class. struct objc_object { Class _Nonnull isa; // objc_object instance pointer}; /// A pointer to an instance of a class. typedef struct objc_object *id;Copy the code

The ID here is defined as a pointer to the objc_Object structure. You can see that the objc_Object structure contains only one ISA pointer of type Class.

In other words, the only thing an Object holds is the address of its Class. When we make a method call on an object, such as [Receiver Selector]; , it will find the corresponding objc_class structure through the ISA pointer to the objc_Object structure, and then find the method we called in the Objc_Class structure’s methodLists and execute.

3.4 Meta Class

The isa pointer to the object (objC_object) points to the corresponding class object (objc_class). What does the ISA pointer to the class object (objc_class structure) point to?

The ISA pointer to the objC_class structure actually points to the Meta Class of the Class object itself.

So what is a Meta Class?

A Meta Class is the Class to which a Class object belongs. The class to which an object belongs is called a class object, and the class to which a class object belongs is called a metaclass.

The Runtime calls the type of a Class object Meta Class, which describes the characteristics of the object itself. In the metaclass methodLists, there is a list of the methods of the Class, called “Class methods.” And the ISA pointer in the class object points to the metaclass. Each class object has one and only one metaclass associated with it.

In 2. The basic principle of message mechanism, we explain the call process of object method. We find the corresponding Class (Class) through the ISA pointer of the object. And then look for the corresponding selector in the method list of the Class.

The process of calling class methods is similar to that of calling object methods, as follows:

  1. By class objectIsa pointerFind one’s ownMeta Class;
  2. inMeta ClassMethod listTo find the correspondingselector;
  3. Execute the correspondingselector.

Here’s an example:

NSString *testString = [NSString stringWithFormat:@"%d,%s".3."test"];
Copy the code

In the example above, stringWithFormat: is sent to the NSString class. The NSString class finds the NSString metaclass through the ISA pointer and then finds the corresponding stringWithFormat in the metaclass’s list of methods: Method, and then executes the method.

3.5 Relationships among instance objects, classes, and metaclasses

Above, we explained the basic concepts of Object, Class, Meta Class, and simple pointing relationships. The following diagram illustrates this relationship clearly.

Let’s start with the ISA pointer:

  1. In the horizontal direction, in each levelInstance objectsIsa pointerIt points to the correspondingClass objectAnd theClass objectIsa pointerIt points to the correspondingThe metaclass. And all metaclassesIsa pointerAnd eventually it points toNSObject metaclass, soNSObject metaclassAlso referred to as theThe root class.
  2. In the vertical direction,The metaclassIsa pointerThe parent class metaclassIsa pointerAll point to theA metaclass. whileThe root classIsa pointerHe pointed to himself again.

Let’s look at the parent pointer again:

  1. Class objectPointer to the parent classPoints to theThe class object of the parent class.The class object of the parent classAnd pointed to theThe class object of the root class.The class object of the root classIt ends up pointing to nil.
  2. The metaclassPointer to the parent classPoints to theThe metaclass of a superclass object.The metaclass of a superclass objectPointer to the parent classPoints to theThe metaclass of the root object, that is,A metaclass. whileA metaclassFather pointerPoints to theThe root class objectThat ends up pointing to nil.

3.6 Methods

The element in the methodLists of the objC_class structure is a Method.

Objc /runtime.h objC/Runtime. h objC /runtime.h objC/Runtime. h

/// An opaque type that represents a method inTypedef struct objc_method *Method; struct objc_method { SEL _Nonnull method_name; Char * _Nullable method_types; // Method type IMP _Nonnull method_IMP; // method implementation};Copy the code

As you can see, the objC_method structure contains the method name (method_name), method type (method_types), and method implementation (method_IMP). Now, let’s look at these three variables.

  1. SEL method_name; / / the method name
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
Copy the code

SEL is a pointer to the objc_selector structure, but is not explicitly defined in the Runtime header. However, we can see from our tests that SEL is just a string that holds the method name.

SEL sel = @selector(viewDidLoad);
NSLog(@"%s", sel); // output: viewDidLoad SEL sel1 = @selector(test);
NSLog(@"%s", sel1); / / output:test
Copy the code
  1. IMP method_imp; // Method implementation
/// A pointer to the function of a method implementation. 
#if ! OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#elsetypedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...) ;#endif
Copy the code

IMP is essentially a function pointer, which points to the implementation of the method. IMP is used to find the function address and then execute the function.

  1. char * method_types; // Method type

The method type method_types is a string that stores the parameter types and return value types of the method.

A Method associates SEL with IMP. When a message is sent to an object, the SEL given is used to find IMP and then executes.


4. Runtime message forwarding

We mentioned in the last step of 2. The basic principle of message mechanism: If the corresponding selector is not found, the message is forwarded or the corresponding implementation method is temporarily added to recever, otherwise a crash will occur.

When a method cannot be found, the Runtime provides dynamic message parsing, message receiver redirection, and message redirection to process the message. The process is shown in the following figure:

4.1 Dynamic message parsing

The Objective-C runtime calls +resolveInstanceMethod: or +resolveClassMethod:, giving you the opportunity to provide a function implementation. We can override these two methods, add another function implementation, and return YES, and the runtime system will restart the process of sending a message.

The main methods used are as follows:

// call when the class method is not found, where you can add the class method implementation
+ (BOOL)resolveClassMethod:(SEL)sel;
// call when object method is not found, can be implemented in this object method
+ (BOOL)resolveInstanceMethod:(SEL)sel;

/** * class_addMethod adds a new method to the class with the given name and implementation * @param CLS the class to which the method is added * @param name Selector method name * @param IMP implementation function pointer * @param Types IMP refers to the return value and parameter types of the function * @return Returns YES on success, NO */ otherwise
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                const char * _Nullable types);
Copy the code

Here’s an example:

#import "ViewController.h"
#include "objc/runtime.h"

@interface ViewController(a)

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Execute fun
    [self performSelector:@selector(fun)];
}

// Override resolveInstanceMethod: add object method implementation
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(fun)) { // If fun is executed, the IMP is dynamically parsed. specify a new IMP
        class_addMethod([self class], sel, (IMP)funMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void funMethod(id obj, SEL _cmd) {
    NSLog(@"funMethod"); // The new fun function
}

@end
Copy the code

2019-06-12 10:25:39.848260+0800 runtime[14884:7977579] funMethod

FunMethod is implemented by overriding resolveInstanceMethod:, adding object methods with class_addMethod, and executing funMethod. From the printed result, the funMethod method was successfully invoked.

Note the special parameter v@: in the class_addMethod method. See the official documentation for Type Encodings: Portal

4.2 Message Receiver Redirection

If +resolveInstanceMethod: or +resolveClassMethod: no other function implementation was added in the previous step, the runtime proceeds to the next step: message recipient redirection.

Implements – forwardingTargetForSelector: if the current object, the Runtime will call this method, allows us to forward the recipient of the message to other objects.

Methods used:

/ / message receiver of redirection method, return to a class or object instance - (id) forwardingTargetForSelector (SEL) aSelector;Copy the code

Note: either +resolveInstanceMethod: or +resolveClassMethod: returns YES or NO, as long as NO other function implementation is added, the runtime proceeds to the next step.

Here’s an example:

#import "ViewController.h"
#include "objc/runtime.h"

@interface Person : NSObject

- (void)fun;

@end

@implementation Person

- (void)fun {
    NSLog(@"fun");
}

@end

@interface ViewController(a)

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Execute fun
    [self performSelector:@selector(fun)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES; // For the next step of message receiver redirection
}

// Message receiver redirects
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(fun)) {
        return [[Person alloc] init];
        // Return the Person object to receive the message
    }
    
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

2019-06-12 17:34:05.027800+0800 runtime[19495:8232512] Fun

As you can see, while the current ViewController does not implement fun, +resolveInstanceMethod: does not add any other function implementations. But we through forwardingTargetForSelector ViewController in the current method is forwarded to the Person object to perform. The printed result also proves that we have successfully achieved forwarding.

We through forwardingTargetForSelector can modify the message receiver, this method returns the parameter is an object, if the object is nil, also is not self, system will run forward messages to this object. Otherwise, proceed to the next step: the message redirection process.

4.3 Message Redirection

If after a redirect message dynamic parsing, the receiver, the Runtime system still can’t find the corresponding methods to the response message, the Runtime system will use – methodSignatureForSelector: method for function arguments and return values of type.

  • if-methodSignatureForSelector:Returns aNSMethodSignatureObject (function signature), which the Runtime system createsNSInvocationObject and pass-forwardInvocation:The message notifies the current object, giving the message sender one last chance to find the IMP.
  • if-methodSignatureForSelector:returnnil. The Runtime system emits-doesNotRecognizeSelector:Message, and the program crashes.

So we can forward the message in the -forwardInvocation: method.

Methods used:

/ / get function parameters and return values of type, return the signature - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector; // message redirection - (void)forwardInvocation:(NSInvocation *)anInvocation;Copy the code

Here’s an example:

#import "ViewController.h"
#include "objc/runtime.h"

@interface Person : NSObject

- (void)fun;

@end

@implementation Person

- (void)fun {
    NSLog(@"fun"); } @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Perform fun [self performSelector:@selector(fun)]; } + (BOOL)resolveInstanceMethod:(SEL)sel {returnYES; / / for the next message recipient redirect} - (id) forwardingTargetForSelector aSelector: (SEL) {returnnil; / / for the next message redirect} / / gain function parameters and return values of type, return the signature - (NSMethodSignature *) methodSignatureForSelector aSelector: (SEL) {if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return[super methodSignatureForSelector:aSelector]; } // message redirection - (void)forwardInvocation:(NSInvocation *)anInvocation {SEL SEL = anInvocation. Person *p = [[Person alloc] init];if([p respondsToSelector:sel]) {// Determine if the Person object method can respond to sel [anInvocation invokeWithTarget:p]; // If it can respond, the message is forwarded to other objects for processing}else{ [self doesNotRecognizeSelector:sel]; }} @endCopy the code

2019-06-13 13:23:06.935624+0800 runtime[30032:8724248] Fun

As you can see, we have the Person object execute fun in the -Forward Invocation: method.

Since – forwardingTargetForSelector: and – forwardInvocation: all messages can be forwarded to other objects, then where is the difference between the two?

The difference is that – forwardingTargetForSelector: only forward the message to an object. The -forwardInvocation: The message can be forwarded to multiple objects.

This is the whole process of Runtime message forwarding.

Combined with the basic principle of 2. Message mechanism mentioned above, the whole process of message sending and forwarding is constituted. Let’s summarize the process.


5. Summary of message sending and forwarding mechanism

Call [receiver selector]; After that, the process is:

  1. Compilation stage:[receiver selector];Methods are converted by the compiler to:
    1. Objc_msgSend (receiver, the selector)(Without parameters)
    2. Objc_msgSend (recevier, selector, org1, org2...)(with parameters)
  2. Runtime phase: message receiverreceverLook for the correspondingselector.
    1. throughrecevierIsa pointerfindrecevierClass (class);
    2. inClass (Class)Cache (method cache)In the hash table ofIMP (Method implementation);
    3. If theCache (method cache)Is not found inIMP (Method implementation)If so, continue inClass (Class)Method listTo find the correspondingselectorIf found, fill toCache (method cache)And returnselector;
    4. If theClass (class)I didn’t find this inselector, just continue in itsSuperclass (superclass)Looking for;
    5. Once we find a matchselector, direct executionreceverThe correspondingselectorMethod implementedIMP (Method implementation).
    6. If you can’t find a matchselectorRuntime The system enters the message forwarding mechanism.
  3. Runtime message forwarding phase:
    1. Dynamic resolution: by rewriting+resolveInstanceMethod:or+resolveClassMethod:Methods: Usingclass_addMethodMethod to add additional function implementations;
    2. Message receiver redirection: Available in the current object if additional function implementations were added in the previous step-forwardingTargetForSelector:Method forwards the recipient of the message to other objects;
    3. Message redirection: if the previous step did not return a value ofnil, the use of-methodSignatureForSelector:Method to get the parameters and return value types of a function.
      1. if-methodSignatureForSelector:Returns aNSMethodSignatureObject (function signature), which the Runtime system createsNSInvocationObject and pass-forwardInvocation:The message notifies the current object, giving the message sender one last chance to find the IMP.
      2. if-methodSignatureForSelector:returnnil. The Runtime system emits-doesNotRecognizeSelector:Message, and the program crashes.

The resources

  • Documentation: Objective-C Runtime (Official Apple documentation)
  • Documentation: Objective-C Runtime Programming Guide (Official Apple documentation)
  • Blog post: Run-time basics for iOS Runtime
  • Blog post: iOS Runtime details
  • Blog post: iOS Runtime tutorial for beginners

That’s it for iOS development: “Runtime” detail (1) : The basics. The whole article mainly talks about one thing: the principle and flow of message sending and forwarding mechanism. This is how the Runtime system works.

In my next post, I’m going to talk about the Runtime’s dark magic Method Swizzling.


IOS Development: Runtime

  • IOS development: “Runtime” details (1) Basic knowledge
  • IOS development: “Runtime” details (ii) Method Swizzling
  • IOS development: “Runtime” details (3) Category underlying principles
  • IOS development: “Runtime” detailed explanation (4) obtain the class detailed attributes, methods

Not yet completed:

  • IOS development: “Runtime” details (5) Crash protection system
  • IOS development: “Runtime” (6) Objective-C 2.0
  • IOS development: “Runtime” details (seven) KVO low-level implementation