The Runtime collection

Ios-isa, superclass Pointers, and the superclass superclass points to the base class itself

1. What is Runtime

Runtime is a library located in usr/include/objc. The commonly used API is located in runtime.h.

2. What does Runtime do

Runtime allows you to dynamically create objects, check objects, modify classes and object methods during App Runtime. Runtime is the basis of objective-C’s Runtime mechanism

3. Basic principles of the message mechanism

Declare a Person class that contains two object methods (personSleep, personSleep, personSleep).

@implementation Person - (void)eatFood:(NSString *)foodName { NSLog(@"person eat food : %@", foodName); } - (void)personSleep { NSLog(@"person is sleeping..." ); } @endCopy the code

We call eatFood from the outside and compile it into CPP to view

    Person *person = [[Person alloc] init];
    [person eatFood:@"baozi"];
    [person personSleep];

Copy the code

cpp

    Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("eatFood:"), (NSString *)&__NSConstantStringImpl__var_folders_44_1ht3l6g55dv59_5s62wsv_bm0000gn_T_ViewController_88ee85_mi_0);
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personSleep"));
Copy the code

To simplify the code, we see that the compiled [Person eatFood] becomes

objc_msgSend(reciver, Selector)

objc_msgSend(reciver, Selector, org1, org2, …)

Run-time phase: Message receiver reciver finds a Selector to execute

  1. throughrecivertheisaA pointer to findrevicertheClass
  2. inClasstheCache (method cache)Find the corresponding hash tableIMP(Method implementation)
  3. If 2. Do not find, continue inClasstheMethod listTo find the correspondingselector, if found, populate the ClasscacheAnd in returnselector
  4. If 3. Is not found, continue to look in its parent class
  5. Once we find a matchselector, direct executionrecivertheselectortheIMP(Method implementation)
  6. If you can’t find a matchselector, requires messages to be forwarded or temporarily directed to thisreciveraddselectorOtherwise a crash will occur

4. Concepts in Runtime

4.1 objc_msgSend

All objective-C methods are compiled as calls to objc_msgSend,

4.2 the Class

struct objc_class { Class _Nonnull isa; //objc_class instance pointer #if! __OBJC2__ Class _Nullable super_class; // Pointer to parent const char * _Nonnull name; // Class name long version; // Class version information, default is 0 long info; // Class information, some bits for run-time use to identify long instance_size; Struct objc_iVAR_list * _Nullable ivars; Struct objc_method_list * _Nullable * _Nullable methodLists; 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. The isa pointer holds a pointer to an instance of the objc_class structure. This is called an object

4.3 the Object

In objc.h, Object is defined as the objC_class structure

/// Represents an instance of a class. typedef struct objc_class *Class; /// 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 objc_object structure contains only one isa pointer of Class type. That is, the only thing an Object holds is the address of its Class. When we call a method on an Object, For example, [Receiver Selector] will find the corresponding object_class structure through the ISA pointer to the objc_Object structure, then find the method we called in the object_class structure’s methodLists, and execute

4.4 Meta Class

As we can see from above, The isa pointer to the object (objc_object structure) points to the corresponding object_class structure. What does the ISA pointer to the class object (object_class structure) point to The ISA pointer to the object_class structure actually points to the meta-class of the class object itself.

A metaclass 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 a metaclass

In Runtime, the class object belongs to a type called meta-class, which describes the characteristics of the class object itself. In metaclass methodLists, there isa list of the methods of the class, which is called the class method, and the isa pointer in the class object points to the metaclass. Each class object has one and only one metaclass associated with it

3. In the basic principle of message mechanism, the object invocation process is to find the class object through the isa pointer of the object, and find the corresponding selector in the methodLists of the class object

The process of calling a class method is similar to that of calling an object, as follows:

  1. throughClass objecttheIsa pointerFind one’s ownMeta - class (class)
  2. inmeta-classthemethodListsTo find the correspondingselector
  3. Execute the correspondingselector

Here’s an example:

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

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

4.5 Relationships among instance objects, classes, and metaclasses

Ios-isa, superclass Pointers, and the superclass superclass points to the base class itself

4.6 Method

The element in the object_class structure’s methodLists is Method.

In objc/runtime.h, the objC_method structure represents the following data structure

struct objc_method { SEL _Nonnull method_name; Char * _Nullable method_types; // Method type IMP _Nonnull method_IMP; // method implementation}Copy the code
  1. SEL method_name Method name

SEL is defined in objc/objc.h

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

SEL is a pointer to objc_selector, 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);
    SEL sel1 = @selector(test);
    NSLog(@"%s", sel1);
Copy the code

The output is:

2021-05-10 21:58:24.705590+0800 RuntimeDemo[2266:67998] viewDidLoad
2021-05-10 21:58:24.705746+0800 RuntimeDemo[2266:67998] test
Copy the code
  1. IMP _Nonnull method_IMP method implementation

IMP is also defined in objc/objc.h

/// A pointer to the function of a method implementation. #if ! OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... * /); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...) ; #endifCopy the code

3. Char * _Nullable method_types; Method Types 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

5. Runtime message forwarding

In the last step of 3. Basic principle of message mechanism, we mentioned that if the corresponding selector cannot be found, the message is forwarded or the corresponding implementation method of the selector is temporarily added to the receiver, otherwise it will crash

When a method cannot be found, the Runtime provides dynamic message parsing, message receiver redirection, and message direction to process the message

5.1 Dynamic Message Parsing (Dynamic Addition Method)

The Objective-C runtime calls +resolveClassMethod or +resolveInstanceMethod, giving you the opportunity to provide a function implementation. The former is called when the object method is not found and the latter is called when the class method is not found. 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

// at objc/ nsobject. h + (BOOL)resolveClassMethod:(SEL) SEL; + (BOOL)resolveInstanceMethod:(SEL)sel; Objc /runtime.h /** * Add new methods to a class, This method requires a name and arguments * * @param CLS class to which the method is to be added * @param name Selector method name * @param IMP implementation function pointer * @param types only want the return value of the function and argument types * * @return Returns YES if the added method succeeds, Otherwise NO */ OBJC_EXPORT BOOL class_addMethod(Class _Nullable CLS, SEL _Nonnull NAME, IMP _Nonnull IMP, const char * _Nullable types) ;Copy the code

Code examples:

// // viewController.m // RuntimeDemo // // Created by Terence on 2021/5/10. // Copyright © 2021 Terence. All Rights reserved. // #import "ViewController.h" #import "objc/runtime.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self performSelector:@selector(eat)]; } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { class_addMethod(self.class, sel, (IMP)eatMethod, "v@:"); return YES; } return [super resolveInstanceMethod:sel]; } void eatMethod(id obj, SEL _cmd) { NSLog(@"eat food"); } @endCopy the code

Output result:

2021-05-10 23:10:23.110858+0800 RuntimeDemo[3451:122697] eat food
Copy the code

ResolveInstanceMethod (resolveInstanceMethod, resolveInstanceMethod, resolveInstanceMethod, class_addMethod

For the special parameter v@: in the class_addMethod method, see the description of Type Encodings in the Official Apple documentation

5.2 Dynamic Message Forwarding

If +resolveClassMethod, +resolveInstanceMethod did not add other function implementations in the previous step, the runtime will proceed to the next step: message receiver redirection

If the current object implementation – forwardingTargetForSelector: or + forwardingTargetForSelector: method, the Runtime can call this method, allow us to forward the message receiver to other objects

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

Note:

  1. Class methods and object methods use different methods for the second step of message forwarding, the former is+forwardingTargetForSelectorMethod, the latter is-forwardingTargetForSelectormethods
  2. here-resolveClassMethod:or-resolveInstanceMethodWhether it returns YES or NO, as long as NO other function implementation is added to it, the runtime proceeds to the next step

Code examples:

@implementation Person - (void)eatFood:(NSString *)foodName { NSLog(@"person eat food : %@", foodName); } - (void)personSleep { NSLog(@"person is sleeping..." ); } @endCopy the code
@implementation ViewController

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

+ (BOOL)resolveClassMethod:(SEL)sel {
    return YES;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return YES;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(personSleep)) {
        return [[Person alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end
Copy the code

Printout:

2021-05-11 11:50:55.352147+0800 LoadInitializeDemo[47468:1985216] person is sleeping...
Copy the code

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

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

5.3 Message Redirection

If the message is dynamically parsed and the message receiver is redirected, The Runtime system still can’t find the corresponding methods to the response message, the Runtime system will use – methodSignatureForSelector: or + methodSignatureForSelector: method for function arguments and return values of type

  • ifmethodSignatureForSelectorReturns aNSMethodSignatureObject (function signature), which the Runtime system createsNSInvocationObject. And through theforwardInvocation:The message notifies the current object, giving the message a chance to find the IMP in turn
  • ifmethodSignatureForSelecotr:Return nil, the Runtime system emitsdoesNotRecognizeSelector:Message, and the program crashes

So we can forward the message in the Forward Invocation: method

Note: Class methods and object methods also use different methods in the third step of message forwarding.

  1. +methodSignatureForSelector
  2. + forwardInvocation:
  3. doesNotRecognizeSelector:

Object method invocation is – methodSignatureForSelector: – forwardingInvocation: doesNotRecognizeSelector:

Methods used

/ / get function parameters and return values of type class methods, return the signature + (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector; // Class method message redirection + (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"aInvocation: %@", anInvocation); / / to get object method function parameters and return values of type, return the signature - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector; // Object method message Redirection - (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"aInvocation: %@", anInvocation); }}Copy the code

Code sample

#import "ViewController.h" #import "Person.h" #import <objc/runtime.h> @interface ViewController () @end @implementation  ViewController - (void)viewDidLoad { [super viewDidLoad]; [ViewController performSelector:@selector(personWakeup)]; } + (BOOL)resolveInstanceMethod:(SEL) SEL {// to proceed to the next step, the message receiver redirects return YES; } / / message receiver redirect + (id) forwardingTargetForSelector aSelector: (SEL) {/ / in order to take the next step, Message to redirect the return [super forwardingTargetForSelector: aSelector]; } // Get the parameters and return value types of the function, Return the signature + (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {if ([NSStringFromSelector (aSelector) isEqualToString:@"personWakeup"]) { return [NSMethodSignature signatureWithObjCTypes:"v@:"]; } return [super methodSignatureForSelector:aSelector]; } // Message redirection + (void)forwardInvocation:(NSInvocation *)anInvocation {NSLog(@"aInvocation: %@", anInvocation); SEL sel = anInvocation.selector; If ([Person respondsToSelector: sel]) {/ / whether the Person class objects can respond to sel [anInvocation invokeWithTarget: Person. The class]; / / if can response, then the message is forwarded to other objects processed} else {[anInvocation doesNotRecognizeSelector: sel]; }} @endCopy the code

Print result:

2021-05-11 16:40:10.119025+0800 LoadInitializeDemo[93832:2252330] person will wake up...

Copy the code

As you can see, we have the Peron object execute the personWakeup function in the + Forward Invocation: method

Since – forwardingTargetForSelector: and – forwardingInvocation: all messages can be forwarded to other objects, then where is the difference? The difference is that – forwardingTargetForSelector: only forward the message to an object, and – forwardingInvocation: messages can be forwarded to multiple objects

This is the whole process of Runtime message forwarding

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

6. Summary of the level-1 forwarding mechanism for sending messages

After calling [Receiver Selector], the flow occurs:

  1. Compile phase: The [Receiver Selector] method is converted by the compiler to:
    1. objc_msgSend(receiver, selector)(Without parameters)
    2. objc_msgSend(receiver,selector, org1, org2, ...)(with parameters)
  2. Runtime phase: The message receiver receiver looks for the corresponding selector
    1. throughreceivertheisaA pointer to findreceivertheClass
    2. Find the corresponding IMP in the hash table of the Class’s cache(method cache)
    3. If theCache (method cache)Is not found inIMP(Method implementation), continues inClass (Class)themethodListsTo find the correspondingselectorIf found, fill toCache (method cache)And returnselector
    4. If theClass (class)I didn’t find this inselector, just continue in itsSuperclass (parent)In looking for
    5. Once we find a matchselectorDirect toreceiverThe correspondingselectorMethod implementedIMP(Method implementation)
    6. If you can’t find a matchselector.RuntimeThe system enters the message forwarding mechanism

3. Message forwarding phase at runtime:

  1. Dynamic resolution: by rewritingresolveInstanceMethodorresolveClassMethod, the use ofclass_addMethodDynamic addition method
  2. Message receiver redirection: Available in the current object if no other function implementation was added in the previous stepforwardingTargetForSelectorForwards the receiver of the message to another object
  3. Message redirection: If the previous step did not return nil and an NSMethodSignature object (function signature) was returned, the Runtime system creates an NSIncovation object and passesforwardingInvocation:The message notifies the current object, giving the message a chance to find the IMP in turn
  4. If the methodSignationForSelector returns nil, the Runtime system will send outdoesNotRecoginzerSelector:Message, and the program crashes

Reference:

  1. Objective-c Runtime apple official documentation
  2. Objective-C Runtime Programming Guide
  3. (1) Basic knowledge