“This is the fifth day of my participation in the First Challenge 2022. For details: First Challenge 2022.”
Understanding the Runtime helps us understand how an Objective-C Runtime system works and how to leverage it. This chapter shows how the NSObject class and objective-C programs interact with the runtime system, find information about objects at runtime, and forward messages to other objects.
The Runtime profile
Runtime, also known as Runtime, is the core of iOS. It is a set of low-level C language APIS. It does some of the work at run time rather than compile time, so there are a lot of classes and member variables that we don’t know about at compile time, and at run time, the code we write gets translated into full, determinate code to run.
Runtime interaction
There are three levels of interaction with a runtime system in Objective-C:
- through
Objective-C
The source code - through
Foundation
In theNSObject
Defined methods - By directly calling the runtime function
Objective – C source code
As the name suggests, you can use it simply by writing and compiling Objective-C code. When you compile code that contains Objective-C classes and methods, the compiler creates data structures and functions that implement the language’s dynamic features.
NSObject methods
In Cocoa, most objects are subclasses of NSObject and therefore inherit the methods he defines. (NSProxy is a special case.) NSObject has methods to simply query information from the runtime system. Object execution introspection is supported. For example, the class methods, isKindOfClass: and isMemberOfClass:, check that the object is in the correct position in the inheritance hierarchy; ConformsToProtocol: whether an object implements the methods defined in a particular protocol; RespondsToSelector, indicating whether the object can accept a particular message;
// Check the object's position in the inheritance hierarchy; - (BOOL)isKindOfClass:(Class)aClass; - (BOOL)isMemberOfClass:(Class)aClass; // indicates whether an object claims to implement a method defined in a particular Protocol - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector; // address of method implementation - (IMP)methodForSelector:(SEL)aSelector;Copy the code
Runtime function
The runtime system is a shared dynamic library with a common interface in the directory /usr/include/objc. It contains functions and data structures, many of which use pure C to replicate what the compiler does when writing Objective-C code. And this is actually what we often see, where you can use some of the runtime functions to achieve the same effect as the NSObject method, and it’s these underlying functions that make up the basic functionality of NSObject. Here is the official objective-C runtime reference.
Now that we know what run-time interactions are, let’s look at some of the basic concepts in Objective-C: classes, objects, methods, SEL, AND IMP. Once you’re familiar with these concepts, you’ll have a better understanding of what the runtime does.
Class, object, Method, SEL, IMP
class
Class objects (classes) are created by the compiler at run time after being set up by the program. When an instance method of an object is called, the Class is found through ISA and then looked up in the list of methods in that Class.
Typedef struct objc_class *Class;Copy the code
Struct objc_class {Class _Nonnull ISA OBJC_ISA_AVAILABILITY; struct objc_class {Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if ! __OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;Copy the code
The structure contains Pointers to the parent class, class names, versions, instance sizes, a list of instance variables, a list of methods, caches, a list of protocols to adhere to, and so on.
Where do class methods exist? When we call a class method, how do we find it? Here’s a concept: meta-classes. (For more on metaclasses, see What is a meta-class in Objective-C?)
A metaclass is the class to which the ISA of a class object points, or the class of a class object
object
An Object is created when we alloc or new a class Object, copying the member variables of the class to which the instance belongs, but not the methods defined by the class. When an instance method is called, the system looks for the method to which the corresponding selector of the message points from the class’s method list and its parent class’s method list based on the instance’s ISA pointer
Struct objc_object {Class _Nonnull isa; OBJC_ISA_AVAILABILITY; };Copy the code
From this, we can conclude that class objects and instance objects have the same lookup mechanism:
- When an instance method of an object is called, the implementation of the method is obtained in the class through the object’s ISA.
- When a class method of a class object is called, the implementation of the method is obtained in the metaclass through the class isa.
The corresponding relationship is shown in the following figure, which describes the relationship among objects, classes and metaclasses:
The solid line is the super_class pointer and the dotted line is the ISA pointer.
- Root class(class) is NSObject, and NSObject has no superclass, so the superclass of Root class(class) points to nil.
- Each Class has an ISA pointing to a unique Meta Class
- The superclass of Root class(meta) points to Root class(class), which is NSObject, forming a loop.
- Each Meta class’s ISA points to Root class (Meta).
Method
A Method is a piece of code that performs a function independently. Method through selector and IMP two properties, to achieve a fast query Method and implementation, relatively improved performance, and maintain flexibility.
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp
Copy the code
SEL
SEL is a method selector, usually written like @selector()
Typedef struct objc_selector *SEL;Copy the code
Sels1 = @selector(test1); SEL s2 = NSSelectorFromString(@"test2");Copy the code
IMP
IMP is a pointer to the memory address of the final implementation. Here is its definition:
Typedef id (*IMP)(id, SEL,...) ; #endifCopy the code
With these concepts in mind, let’s get down to business.
In Objective-C, messages are not bound to method implementations until runtime. The compiler transforms the message expression and calls the objc_msgSend method.
Message is sent
All method calls/class generation in Objective-C takes place at runtime, we can get the corresponding class and method by class name/method name reflection, or we can replace a method of a class with a new implementation, and theoretically you can call any Objective-C method at runtime by class name/method name, Replace the implementation of any class and add any classes.
For example, if we write a call method [Receiver Message], this method is converted by the compiler to:
// The first parameter type is sender, and the second parameter type is SEL. SEL select. select. objc_msgSend (id _Nullable self, SEL op...) ;Copy the code
Methods with the same name in different classes have the same method selector, even if the method name is the same but the variable type is different. Because of this feature, objective-C does not support function overloading.
In fact, the process of the method we’re calling is actually message sending in the Runtime.
Objc_msgSend is implemented by assembly language, according to the CPU architecture implementation process varies, if you want to read the relevant code must have a certain assembly foundation;
Objc_msgSend does a few things:
-
1. Check whether the selector is to be ignored
-
2. Check if target is nil
- If there’s a nil handler, jump to the nil handler
- If there is no function to handle nil, it is automatically cleaned up and returned. This is why
Objective-C
The reason why sending messages to nil does not crash
-
3. After determining not to send a message to nil, look for the IMP implementation of the method in the cache of that class
- If found, jump to execute
- If it doesn’t, keep looking in the method list until you find NSObject
-
4. If you do not find the message, you need to start the message forwarding phase. At this point, the Messaging phase is complete. This stage is to quickly find the IMP through select()
Messaging framework:
To speed up the messaging process, the runtime system caches the method’s selectors and addresses when a method is used. Each class has a separate cache that can contain inherited methods as well as selectors for methods defined in the class. During message delivery, the cache of the receiving object class is first checked.
forward
In the forward phase, _objc_msgForward(id self, SEL _cmd…) is called. methods
_objc_msgForward(id _Nonnull receiver, SEL _Nonnull sel, ...)
Copy the code
_objc_msgForward does the following things:
- 1. The first call
forwardingTargetForSelector
Method gets the new target as receiver and executes the selector again,- If the returned content is valid, skip to execution
- If the returned content is illegal (nil or the same as the old receiver), continue with the subsequent methods.
- 2. Call
methodSignatureForSelector
After obtaining the method signature, determine whether the returned type information is correct, and then callforwardInvocation
performNSInvocation
Object and returns the result. If the object is not implementedmethodSignatureForSelector
Method to proceed to the later method. - 3. Call
doesNotRecognizeSelector
Method to throw an exception.