The opening

I actually encountered a few questions, but the answer is not good, start…

  1. Describe the flow of object method calls
  2. How do ISA Pointers find corresponding classes
  3. After finding the corresponding implementation in the method list, whether to save it in the cache of the current class or in the parent class
  4. There is no uniform method class for handling such a no-method crash
  5. Under what conditions will the message forwarding stage be entered
  6. What are the practical applications of Runtime during development

MJ Interviewer recommends that you “dig into a problem thoroughly and find a good article you can find. You can’t just read blogs.” You have to convert it into your own knowledge and digest it. It’s a great idea, but the good thing is that it’s not going to work. Unfortunately, I didn’t realize it until now.

This stage is the progression of the Runtime chapter!

Runtime related issues (mentioned in the article)

1. Relationship between [obj foo] and objc_msgSend()

What does [obj foo] (message passing or sending a message to an obj object foo) have to do with objc_msgSend()?

[obj foo], after being processed by the compiler, becomes the objc_msgSend() function. The first argument is obj and the second argument is a selector of foo. Since [obj foo] has no other arguments, So the objc_msgSend() function has only two arguments. After compiling, it converts [obj foo] to objc_msgSend(), and the Runtime message passing process beginsCopy the code

2. How does runtime find the corresponding IMP address by Selector?

In fact, the essence of the message passing mechanism, first look for the current instance of the corresponding class object cache, whether there is a corresponding cache IMP implementation, if there is, then return to the caller. If not, then according to the method list of the current class, to find the corresponding IMP Selector, the current class if not, then according to the superClass pointer of the current class to find the parent class method list, and then find the corresponding IMPLEMENTATION of the IMP SelectorCopy the code

3. Can I add instance variables to the compiled class?

The Runtime supports dynamic addition of classes at runtime, noting whether they are compiled or dynamically added Class_ro_t is readonly, so it cannot be modified after compiling. Therefore, the compiled class cannot add instance variables to itCopy the code

4. Can I add instance variables to dynamically added classes?

However, it is possible to add instance variables to dynamically added classes, as long as the instance variables are added before calling its registered class pair methodCopy the code

5. Have you ever used a compiler keyword like @dynamic?

So when we declare a property and we implement it as @dynamic, that means that the get and set methods are added at Runtime, not declared at compile time and actually implemented, and that's what we're looking at at RuntimeCopy the code

6. Differences between compile-time and dynamic runtime languages

1. Dynamic language runtime function resolution will be put off to the runtime, is actually at run time to go for the method to add specific executive functions when we attribute identification for @ dynamic, represent the don't need a compiler to generate this property for us at compile time the get method and set the specific implementation, but rather at runtime when we call the get and concrete Set method, then add a concrete implementation to it. Only dynamic runtime languages support this feature. Compile-time languages make function decisions at compile time. Implementation decisions are made at compile time and cannot be changed at run timeCopy the code

Summary of 0.

The questions that are often asked are:

  1. What’s the difference between a compile-time language and a dynamic runtime language like Objective-C?
  2. What is the difference between message passing and function call?
  3. How does the system implement the message forwarding process for us when we call a method that is not implemented?

In view of these problems, the knowledge structure is developed

  • Basic data structure
  • How to understand object, class object, metaclass object? What was their relationship like? Including: the relationship between instance objects and class objects, the relationship between class objects and metaclass objects.
  • What is the messaging mechanism in OC
  • How do we cache method lookups in the process of method lookups? What is a method caching mechanism used by the system or RunTime?
  • What is the process of message forwarding
  • Method-swizzling is a specific application of Runtime to replace some Method implementations at Runtime
  • Dynamic addition method
  • Dynamic method parsing

1. Data structure

The Runtime base data structures are objC_object, objC_class, ISA pointer, method_t

0. Runtime data structure ✨

  1. Starting with objc_class as a structure, objc_class inherits from Objc_Object
  2. Objc_object has a member variable isa_t. The ISA_t pointer points to an object of type objc_class (or metaclass).
  3. Objc_class consists of three main member variables: superClass refers to the parent of the current class, cache_t is used for caching method lookups during messaging, and class_data_bits_t (some basic information about the class: The list of member variables, attributes, and methods defined/added by the class is in this data structure.
  • SuperClass: actually of type class, it points to such a pointer of type objc_class
  • Cache_t: Is actually a Hash table filled with bucket_t data structures
  • Class_data_bits_t: It encapsulates the data structure of class_rw_T, which contains (read only information related to class_RO_T, protocols, properties, methods).

Class_ro_t contains the name of the name class, the list of methods of the methodList class — method_T, the member variables of the ivars declared class, the attributes of the class, and the protocol of the class

1. objc_object

All objects we use are of type ID. In Runtime, ID is the objc_Object structure.

The objc_object structure contains:

  1. Isa_t: shared
  2. Methods related to isa operations: use the objc_object structure to get the class object to which ISA refers, or use the isa pointer to the class object to get some methods of the metaclass.
  3. Weak reference correlation: Marks whether an object has a weak reference pointer
  4. Associative object methods: Associative objects that we set for objects. Some methods on associative properties are also embodied in the objc_Object structure
  5. Implementation of memory management-related methods: Retain Release, which is often used under MRC, is also implemented in the objC_Object structure

2. objc_class

Class in OC represents a Class that corresponds to the objc_class data structure in Runtime

Objc_class inherits from objc_Object

So a Class like Class is also an object, called a Class object

The objc_class structure consists of:

  1. Objc_class has a superClass pointer to the Class type. Class is a Class object, so superClass points to its superClass object. This is actually defined through the superClass in objc_class.
  2. Cache member variables, method cache structures, and data structures used for message delivery
  3. Bits data structures. Variables, methods, and attributes defined by a class are contained in bits, a member structure

3. Isa pointer

It’s a C++ Commons, and the name in OC is isa_t, which isa zero or a one on both 64-bit and 32-bit architectures

Isa type

There are two types of isa Pointers:

  1. The entire contents of the 64-bit 0 or 1 represent the address of the Class, that is, the address of the Class object can be obtained from the contents of the ISA.
  2. Non-pointer ISA, where the portion of the value represents the address of the Class. This is because we can find all the Class addresses in 30 or 40 places. The extra portion can be used to store other related content, thus saving memory.
Isa to

Object, it points to a Class object and we have an instance, which is the id of the object in OC, objc_Object in Runtime, and there’s an ISA pointer in there that points to its Class

In the case of Class objects, it points to metaclass objects. Class inherits from Objc_Object, so there’s also an ISA pointer in there that points to its metaclass

When we make a method call, when we call an instance method of an instance, we’re actually looking up the method in its class object through the ISA pointer if we’re calling a class method, through the ISA pointer of the class object to look up the method in its metaclass object.

4. cache_t

  1. A structure that uses quick lookup methods to execute a function. When we call a method, if there is a cache, we do not need to iterate through the method list, which can improve the method call speed
  2. Cache_t is an incrementally scalable hash table structure. As the structure grows, cache_t incrementally expands its memory to support more caches. This data structure is implemented with hash tables to improve lookups
  3. Cache_t is the best application of the principle of computer locality, where a few methods are called more frequently when a method is called, and if you put them in the method cache, the hit ratio will be higher next time

Cache_t data structure

Cache_t is understood to be implemented in arrays

  • Each object is a structure like bucket_t. Bucket_t has two main member variables, key and IMP. Key corresponds to the SELECTOR in OC, which calls the method SEL

  • IMP is a function pointer without type. You can find the specific implementation of this method through the name key of the method selector

If you have a key, you can use a hash lookup algorithm to find where in the array bucket_t is located, and then extract the IMP corresponding to bucket_T from that location

5. class_data_bits_t

This structure is a member structure in objc_class

class_rw_t

  • Class_data_bits_t is the encapsulation of class_rw_t
  • Class_rw_t represents the read and write information related to the class, such as the methods, attributes, protocols, etc. It is also the encapsulation of class_ro_t. We create classes at any time to add attributes or methods to the class
  • Class_ro_t represents read-only information about a class. When a class is created, it adds a list of member variables or methods that cannot be changed later

So let’s say we add category A, and all of the methods in category A are in the first column column, and then we add category B, and all of the methods in category B are in the second column column, and all of the methods in category B are in the second column column

class_ro_t

Method_t is actually an abstract description of the methods that are stored in the list of methods that are typically added to the classification

method_t

Method_t is a wrapper around the four elements of a function (name, return value, parameters, and function body) that make the function unique

Method_t is a structure with three main data types

  1. name
  2. The types function returns a collection of values and arguments
  3. Imp no type of function pointer, corresponding to the function body

How do types represent return values and arguments

The types member attribute actually expresses the structure: The first position is always the return value type of the function, followed by the argument type of each argument. There can be multiple arguments, and only one return value is returned. V@: represents the contents of the types.

Class objects and metaclass objects

What are class objects and metaclass objects, and what are the differences between them?

  1. Class objects are structures that store information such as the list of instance methods. Instance objects can access instance methods by finding their own class objects through isa Pointers.
  2. A metaclass object isa structure that stores information such as the list of class methods. A metaclass object can access class methods by finding its own metaclass object through isa Pointers

The red arrow indicates: superClass pointing; The black dotted arrow represents the pointer to isa

  • Root class stands for Root class, and the parent class points to nil, which is NSObject; For this class object, there are subclasses Superclass(class) and subclasses Subclass(class)
  • When we give an instance, the instance of Subclass on the left is of type ID, i.e. the objc_Object data structure. The instance contains a pointer to isa, which points to the corresponding class object of the instance. For example: If you have a class that creates an instanceA, then its ISA points to ClassA
  • The middle row of class objects is the objC_class structure. On the right side are the metaclass objects of the subclass, parent class, and root class respectively. The metaclass objects are also inherited.
  • Class objects also have isa Pointers to metaclass objects

Note that metaclass objects also have isa Pointers because they are of type objc_class.

  1. For any metaclass object, including the root metaclass itself, its ISA pointer points to the root metaclass object
  2. The superClass pointer to the root metaclass object points to its root object

The differences and relationships between class objects and metaclass objects

  1. Instance objects can find their own class objects through isa Pointers, which store the list of methods and other information
  2. A class object can find its metaclass object through the ISA pointer, which gives it access to some information about the list of class methods
  3. Both class objects and metaclass objects are objc_class data structures, and because objC_class inherits from ObjC_Object, they both have isa Pointers, hence the descriptions above

If we call a class method, we look it up in the list of metaclass methods, and we look it up in the red arrow, and when we call a class method that’s not found in the metaclass, we look for the instance method implementation of the same name in the root class object.

If we call a class method and there is no corresponding implementation, but there is an instance method implementation with the same name, does it crash? Will there be an actual call?

  1. The actual call is made because the superClass pointer to the root metaclass object points to the root metaclass object
  2. So if you can’t find the implementation of a class method in a metaclass, you follow the superClass pointer to the list of instance methods
  3. If there is an instance method of the same name, it will actually be called

3. Messaging

Messaging flow

A brief description of the message passing process. The important thing about this problem is that whether an instance method or a class method is called needs to be described separately

  1. If an instance method is called, the system will find its class object according to the isa pointer of the current instance, and traverse the method list in the class object. If no method is found, the system will follow the SubClass pointer to search the method list of the parent object until it reaches the method list of the root class object. If no method is found, the system will search the method list of the root class object. The message forwarding process is displayed
  2. If a class method is called, the system will look for the metaclass object through the ISA pointer of the class object, and then traverse the list of methods to the root metaclass object, and then to the root metaclass object. If there is no method implementation, the message forwarding process will be entered
  3. The difference between a traversal instance method and a class method is that in the root metaclass, the superClass that points to the root metaclass refers to the root object

Messaging mechanism

What is the messaging mechanism in Objective-C?

  1. When a method is called, the cache is searched to see if there is a method in the cache with the corresponding selector name
  2. If so, the function is called through the function pointer and the message is passed
  3. If it is not in the cache, it will look up the list of methods of the current class object according to the isa pointer of the current instance. If it is, it will call this function through the function pointer to end the message passing. If there is no superClass pointer of the current class object, it will search in the list of superClass methods step by step. If there is no superClass pointer of the parent, it will search up according to the superClass pointer of the parent until nil. If there is no superClass pointer of the parent, it will enter the message forwarding process

Summary: first check the cache, cache to find the method list of the current class, the method list of the current class in order to find its parent class method list, are transmitted to the message forwarding process.

Specific cache lookup process: according to the given method selector name (SEL), find the corresponding method implementation (IMP) in (bucket_t)

  1. Cache lookup is a hash lookup for cache hits
  2. If the list of current class methods hits, sorted is binary lookup and unsorted is general lookup
  3. The superClass pointer is used to search the method list of the parent class step by step. In the parent class, the method list of the parent class is also searched first in the cache and then in the parent class
objc_msgSend

  1. The objc_msgSend function is fixed to receive two arguments, self of type ID and the method selector name of type SEL, followed by the actual method arguments of the message passing
  2. For any message passing [self Class] function call that is converted by the compiler to objc_msgSend, the first argument is self, the receiver of the message being passed, and the second argument is the name of the message being passed, the selector name
  3. For message passing, at the compiler level, it actually translates to a function call, [self Class] —- objc_msgSend(self,@selector(Class))

Objc_msgSend (self,@selector(Class)) takes a fixed set of two arguments, The first is the recipient of the message, the second is the name of the message being passed, which is the selector name, followed by the actual method argument of the message being passed

objc_msgSendSuper

  1. There are also two fixed parameters,objc_super *superPointer to the structure type, SEL opMethod selector, followed by the actual arguments (…)
  2. The objc_super structure contains the member variable receiver, which is the current object, for the following reasons: Super is the compiler keyword, which, when compiled by the compiler, is parsed into an objc_super structure pointer, in which the member variable receiver is the current object
  3. Therefore, the receiver of either the message calling [self class] or [super class] is the current object

[self class] differs from [super class]

Take a look at this exam paper. When I met this question, I took it for granted…

First of all, we need to be clear,

  1. For [self class], which is converted to the objc_msgSend function call,
  2. For [super class] function calls that are converted to objc_msgSendSuper, the first argument to this method is super, but the super structure contains the receiver member variable, which is the current object.
  3. So, the recipient of both methods is the current object

If the Phone instance is now initialized at 8, and we print the class information through [self class], we will find the Phone class object through the ISA pointer of 8. 14 Look for the class method here, which itself does not exist. And then it’s going to go up through the superClass pointer, and it’s going to find the Mobile parent class number six, and it doesn’t have a class implementation here, but it’s going to find the root class object number five, which is NSObject, and it has a class implementation, and it’s going to call the class concrete implementation, and it’s going to get back to the caller, and it’s going to print out Phone

id objc_msgSend(id self, SEL op, ...) ; /* The return value is an id, objc_msgSend the essence of the message is that the first argument self is the object pointer to the current response message, recording the address of this object SEL type is similar to the C language function pointer, recording the list of methods. In OC, that's the method name. */ NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));Copy the code

For [super Class], the actual recipient is still the Phone instance, calling objc_msgSendSuper, which starts with the parent method list 6, across the current Phone Class method list 14, But number 6 doesn’t have a Class method either, so it keeps finding that the root Class NSObject has a Class, and the receiver is still the current object (id)self, and it still returns Phone

id objc_msgSendSuper(struct objc_super *super, SEL op, ...) ; /* struct objc_super *super struct pointer type -->struct objc_super{id receiver; Class super_class; } receiver: records the receiver of the message super_class: indicates what the parent class of super is. Super is the method that calls the parent class. */ NSLog((NSString *)&__NSConstantStringImpl__var_folders_cz_l7gn4kjj52x1y4xtvjjvy_l40000gn_T_Student_28e7c0_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super) {(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"))));Copy the code

Both are phones

Method caching

Cache lookup process

It’s a little bit easier to understand by combining these two pictures

The process of finding the cache is essentially finding the corresponding method implementation (IMP) in bucket_t based on the given method selector name (SEL).

The location of bucket_t in the cache_T array is mapped by f(key) based on the given SEL. This step is a hash lookup

So how does hash lookup work?

It is through the hash search algorithm, key&mask, that is, the selector factor key and the corresponding mask are located in the operation, and the value calculated is the index position of the given value in the corresponding array, which improves the search efficiency

After the bucket_t corresponding to SEL is found, the corresponding IMP function pointer can be extracted and returned to the caller

Find in the current class

The current class has a list of corresponding methods,

  • For the sorted method list, binary search algorithm is used to find the corresponding implementation function of the method
  • For an unsorted list of methods, general traversal is used to find the corresponding implementation of the method’s execution function

The parent class is searched level by level

  1. The superClass of the current class is used to find the parent class. After transferring the current class to the parent class, the first step is to determine whether the parent class is nil. If it is nil, the search process ends

Note: NSObject’s superClass pointer is nil

  1. If there is a parent class, the corresponding method implementation is looked up in the parent’s cache

According to the selector factor of the current method, the corresponding method implementation is found in the cache, and the stepwise search process of the parent class is ended. 3. If you don’t find the method implementation for the method selector in the superClass cache, you need to go through the list of methods of the parent class of the current class, see if there is a corresponding method implementation and return if there isn’t, keep going through the parent class of the current class, all the way up the superClass pointer until you get to NSObject, When we take the parent class to nil, we end the search process

conclusion

  1. Cache lookup is a hash lookup for cache hits
  2. If the list of current class methods hits, sorted is binary lookup and unsorted is general lookup
  3. If the list of superClass methods is hit, the superClass pointer is used to search the superClass. In the superClass, the cache is searched first, and then the superClass is searched

5. Message forwarding

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:

+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(test)) { NSLog(@"resolveInstanceMethod"); Class_addMethod ([self class], sel, (IMP)funMethod, "V@:"); return YES; } else {/ / return the parent class's default call return [super resolveInstanceMethod: sel]; } } void funMethod(id obj, SEL _cmd) { NSLog(@"funMethod"); } - (id)forwardingTargetForSelector:(SEL)aSelector { if (aSelector == @selector(test)) { NSLog(@"forwardingTargetForSelector"); // return [[RuntimeTest alloc] init]; return nil; } else { return [super forwardingTargetForSelector:aSelector]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { if (aSelector == @selector(test)) { NSLog(@"methodSignatureForSelector:"); // if the test method is entered, return the correct method signature // v for void @ for id of the first argument type, i.e. Self: Return [NSMethodSignature signatureWithObjCTypes:"V@:"]; } else { return [super methodSignatureForSelector:aSelector]; } } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"forwardInvocation"); }Copy the code

6. Method-Swizzling

One is selector1, the corresponding Method implementation is IMP1 and the other is selector2, the corresponding Method implementation is IMP2 through method-Swizzling

You can modify the actual method implementation that the selector corresponds to,

When we send selector1 to this object, IMP2 is executed, and when we send selector2, IMP1 is executed

Method Swizzling occurs at runtime, essentially swapping two methods. Method Swizzling can be written anywhere, and the swap takes effect after the code is executed

Each class has a Method list, the essence is to class SEL and IMP one by one correspondence,Method Swizzling is the Method list operation, let SEL correspond to other IMP

OC Runtime feature, when calling a method of a class object, the internal is to send a message to the class object, we through the object’s class method list, here to find the corresponding SEL, and SEL corresponds to IMP, we find the implementation of the method through IMP

+ (void)load {
    Method test = class_getClassMethod(self, @selector(test));
    Method otherTest = class_getClassMethod(self, @selector(otherTest));
    method_exchangeImplementations(test, otherTest);
}

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

- (void)otherTest {
    [self otherTest];
    
    NSLog(@"otherTest");
}
Copy the code

7. Dynamic add method

Have you ever used a system method called performSelector?

+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(test)) { NSLog(@"resolveInstanceMethod"); Class_addMethod ([self class], sel, (IMP)funMethod, "V@:"); return YES; } else {/ / return the parent class's default call return [super resolveInstanceMethod: sel]; } } void funMethod(id obj, SEL _cmd) { NSLog(@"funMethod"); }Copy the code

A class that doesn’t have a method at compile time and produces it at Runtime needs to call performSelector to see how the Runtime dynamically adds methods. In the example above, we called test but did not implement it, so we need to add its implementation at runtime to the test method (selector) at resolveInstanceMethod

8. Dynamic method parsing

This section has been moved to the Q&A section on “Runtime related questions”!