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:
- What is Runtime?
- The fundamentals of the messaging mechanism
- Concept parsing in Runtime
- Runtime Message Forwarding
- 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?
- Compilation stage:
[receiver selector];
Methods are converted by the compiler to:Objc_msgSend (receiver, the selector)
(Without parameters)Objc_msgSend (recevier, selector, org1, org2...)
(with parameters)
- Runtime phase: message receiver
recever
Look for the correspondingselector
.- through
recevier
的Isa pointer
findrecevier
的Class (Class)
; - in
Class (Class)
的Cache (method cache)
In the hash table ofIMP (Method implementation)
; - If the
Cache (method cache)
Is not found inIMP (Method implementation)
If so, continue inClass (Class)
的Method list
To find the correspondingselector
If found, fill toCache (method cache)
And returnselector
; - If the
Class (Class)
I didn’t find this inselector
, just continue in itsSuperClass (superClass)
Looking for; - Once we find a match
selector
, direct executionrecever
The correspondingselector
Method implementedIMP (Method implementation)
. - If you can’t find a match
selector
, the message is forwarded or temporarily directed torecever
Add theselector
The corresponding implementation method, otherwise a crash will occur.
- through
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:
- By class object
Isa pointer
Find one’s ownMeta Class
; - in
Meta Class
的Method list
To find the correspondingselector
; - Execute the corresponding
selector
.
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:
- In the horizontal direction, in each level
Instance objects
的Isa pointer
It points to the correspondingClass object
And theClass object
的Isa pointer
It points to the correspondingThe metaclass
. And all metaclassesIsa pointer
And eventually it points toNSObject metaclass
, soNSObject metaclass
Also referred to as theThe root class
. - In the vertical direction,
The metaclass
的Isa pointer
和The parent class metaclass
的Isa pointer
All point to theA metaclass
. whileThe root class
的Isa pointer
He pointed to himself again.
Let’s look at the parent pointer again:
Class object
的Pointer to the parent class
Points to theThe class object of the parent class
.The class object of the parent class
And pointed to theThe class object of the root class
.The class object of the root class
It ends up pointing to nil.The metaclass
的Pointer to the parent class
Points to theThe metaclass of a superclass object
.The metaclass of a superclass object
的Pointer to the parent class
Points to theThe metaclass of the root object
, that is,A metaclass
. whileA metaclass
的Father pointer
Points to theThe root class object
That 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.
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
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.
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 aNSMethodSignature
Object (function signature), which the Runtime system createsNSInvocation
Object 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:
- Compilation stage:
[receiver selector];
Methods are converted by the compiler to:Objc_msgSend (receiver, the selector)
(Without parameters)Objc_msgSend (recevier, selector, org1, org2...)
(with parameters)
- Runtime phase: message receiver
recever
Look for the correspondingselector
.- through
recevier
的Isa pointer
findrecevier
的Class (class)
; - in
Class (Class)
的Cache (method cache)
In the hash table ofIMP (Method implementation)
; - If the
Cache (method cache)
Is not found inIMP (Method implementation)
If so, continue inClass (Class)
的Method list
To find the correspondingselector
If found, fill toCache (method cache)
And returnselector
; - If the
Class (class)
I didn’t find this inselector
, just continue in itsSuperclass (superclass)
Looking for; - Once we find a match
selector
, direct executionrecever
The correspondingselector
Method implementedIMP (Method implementation)
. - If you can’t find a match
selector
Runtime The system enters the message forwarding mechanism.
- through
- Runtime message forwarding phase:
- Dynamic resolution: by rewriting
+resolveInstanceMethod:
or+resolveClassMethod:
Methods: Usingclass_addMethod
Method to add additional function implementations; - 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; - Message redirection: if the previous step did not return a value of
nil
, the use of-methodSignatureForSelector:
Method to get the parameters and return value types of a function.- if
-methodSignatureForSelector:
Returns aNSMethodSignature
Object (function signature), which the Runtime system createsNSInvocation
Object 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.
- if
- Dynamic resolution: by rewriting
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