1. loadClass methods andinitializeClass method invocation process analysis

1.1 loadClass method invocation process analysis

The load class method is called before the main function of the application runs, that is, during the application’s load startup phase, dyld calls the load_images function in objc when the application is loaded, and the load class method of the main class is called and executed. So first take a look at the code for the load_images function, as shown below:

First of all, the first if statement to didInitialAttachCategories have (initialize all attached classification) and didCallDyldNotifyRegister (is called _dyld_objc_notify_register the function, After _objc_init calls _dyLD_OBJc_NOTIFy_register it sets this local static variable to true, Calling _dyLD_objC_notify_register is equivalent to calling the registerObjCNotifiers in dyld, which in turn calls the map_images function in objc, If all non-lazily loaded OC main class data has been loaded, but all attached class data has not been initialized, The loadAllCategories() function is then called to attach the non-lazily loaded OC class to the main class one after another with the attachCatgories function (see article iOS Class Loading Process Analysis for details on this process). The second statement calls hasLoadMethods to determine whether there is a non-lazily loaded main class or non-lazily loaded class in the current image file. If there is no non-lazily loaded main class or non-lazily loaded class in the current image file, the code is as follows:

Prepare_load_methods (prepare_load_methods) ¶ Prepare_load_methods (prepare_load_methods) (prepare_load_methods) (prepare_load_methods) (prepare_load_methods)

The code in this function is divided into two parts. The first part, in red box 1, first calls the _getObjc2NonlazyClassList function to get all the non-lazily loaded main class tables in the image file. We then call schedule_class_load to load the main class and its corresponding load method into the table. Schedule_class_load code looks like this:

Schedule_class_load (); schedule_class_load (); schedule_class_load (); Then add this class and its corresponding load method to the loadable_classes table by using the add_class_to_loadable_list function. In other words, the load method from the parent class will first be added to loadable_classes. The code looks like the following:

Loadable_classes is actually a pointer to an array of loadable_class structure types. The code looks like this:

The logic in the add_class_to_loadable_list function is as follows: first, call getLoadMethod to iterate over all Methods in the baseMethods metaclass ro of CLS. If you find a Method whose Sel is load, Return IMP for this Method, otherwise return nil, as shown below:

Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated: Allocated Allocated Tokens RealLoc is used to expand (2 * loadable_classes_allocated + 16) loadable_classes Then add the CLS and the corresponding load class method to the loadable_classes location.

The code in red box 2 of prepare_load_methods (prepare_load_methods) first gets the categoryList of all non-lazy-loaded categories, then iterates through all the categories in it, first calls the remapClass function to determine if the category is null, and then skips the loop. If it is not empty, the realizeClassWithoutSwift function is called to force a non-lazy load of the data that the class belongs to, Then call the add_category_to_loadable_list function to add the category and its corresponding load class method to the loadable_Categories table. Loadable_categories data structure and type are shown below:

The add_category_to_loadable_list code looks like this:

You can see that this is exactly the same logic as adding the main class and its load class method to the loadable_classes table, except in the class case it’s the IMP that calls the _category_getLoadMethod function to get the load class method in the classMethods table in the class, Its code is shown in the figure below:

After fetching all the non-lazily loaded main classes and classes and their corresponding class methods, the load_images function calls call_load_methods, calling these class methods. The code is shown in the figure below:

As you can see, there are some restrictions in this function. We define a static local variable loading to ensure that both the main class and the load class method of the class are executed once and are called from autoreleasePool. If loadable_classes_used is not greater than 0, call_class_loads will be called all the time, as shown in the following code:

You can see that in this function, we first get the address of the loadable_classes table classes, set the static global loadable_classes to nil, Allocated classes and allocated loadable_classes_used are allocated and assigned to 0. Then, iterate through the elements in the previous classes table to get the implementation of each class and its load method. Then, directly call the implementation of the load method. CLS and the SEL of the load class method are passed as parameters, and classes are destroyed after traversal. This is the process of calling the load class method of the main class.

Call_category_loads function call_category_loads function call_category_loads function call_category_loads function call_category_loads

That’s a detailed look at the load_images function, but you just need to know the general process, and be able to dictate it as follows:

  1. ifObjCThe initialization has been completed,dyldCall aregisterObjCNotifiersFunction is called before all the classification data has been loadedloadAllCategoriesFunction to load all category data.
  2. Then there are non-lazy-loaded classes or non-lazy-loaded classes that are called firstprepare_load_methodsFunction is implemented in all non-lazily loaded main classes and in the parent classes of each main class, respectivelyloadClass methods and their classesloadThe class methodselAdded to theloadable_classesTable, and then classify all non-lazy loads as wellloadClass method implementations are added toloadable_categoriesIn the table.
  3. callcall_load_methodsFunction, first iterateloadable_classesEach in the tableloadable_classStructure variable that calls the execution of each classloadClass method, and then destroy to free the table, and then iterateloadable_categoriesEach of the tablesloadable_categoryStructure variables that are invoked to perform each categoryloadClass method, and finally destroy to free the table.

1.2 Method Invocation Process analysis of the Initialize class

The initialize class method is invoked when the class first sends a message, primarily in the lookupImpOrForward function. Part of the code looks like this:

The above code shows that the initialize method is conducted realizeAndInitializeIfNeeded_locked function call, the code as shown in the figure below:

Among them:

  • Red box 1 code: Before calling the initialize class method of a class, we need to determine whether the class is implemented, that is, whether all its instance methods, properties, protocols, and class methods have been loaded into the corresponding class data storage structure. If not, Will need to call realizeClassMaybeSwiftAndLeaveLocked function realization, and the function of lower level is called the familiar realizeClassWithoutSwift function, but more elaborated here, For details, see iOS Class loading Process Analysis (2).

  • Red box 2: The initializeAndLeaveLocked function is called. The code looks like this:

The initializeAndMaybeRelock function is called again, and the code looks like this:

The code logic in this function is that the isInitialized function is called to determine whether the class isInitialized. If the isInitialized function is called, the program will not proceed. This ensures that the initialize class method is called only when the class first sends a message, or when the parent class implements the initialize method. The initialize class method is not called again when the class first sends a message.

Then call getMaybeUnrealizedNonMetaClass function to obtain a may not realize the metaclass, this line is to ensure that the current incoming class is not a metaclass, if is a metaclass, you get it a metaclass (must obtain the metaclass because, The initialize class method is in the metaclass’s list of methods. You cannot send an initialize message to your metaclass. This will cause an error), but the non-metaclass may not have been implemented yet. Call realizeClassMaybeSwiftAndUnlock function, and then call initializeNonMetaClass function of the non metaclass class the initialize method is invoked, its code is shown below:

First, in this function, we get the parent class of the current class. If the parent class exists and has not called the initialize class method, then we call the initializeNonMetaClass function recursively. This is to ensure that, If the initialize method is implemented in the parent class, then the initialize method in the parent class is called first, and then the initialization state of the class is changed to initialized. Then the callInitalize function is called. The code looks like this:

In this function, objc_msgSend is called to send a message, the receiver is the current class, and the method number is Initialize, which is called to the initialize method in the metaclass method list of the executing class.

If neither this class nor its parent class implements the initialize method, then the initialization process will always call objc_msgSend to send the initialize message. So if you can’t find this method in the metaclass of this class or in the superclass of this class, you’re not going to get an unrecognized selector error and crash? No, the reason is that if you don’t implement the initialize method in your class, but by default the root class NSObject implements the initialize method, that means the initialize method exists in the root metaclass’s method list, No matter whether a subclass implementation of NSObject does not implement this class method, it will not report an error.

2. Order of method invocation (loadClass methods,initializeClass method, the main programc++Constructor, main function)

  • The load method of the main class is called first when the load_images function is called. (If the main class is compiled first, the load method is called first. If both the parent class and the subclass implement the load method, the parent class is called first.) Then there is the Load class method in the class (the class is compiled first, and the load class method is called first).

  • The second is the call to the C++ constructor in the main program. (See iOS App loading Process Analysis for more details.)

  • The third is the call to the main function.

  • Finally, the main program class calls its Initialize class method the first time it sends a message.

The class initialize method can actually be called in one of three ways

  1. Both the parent class and the child class are implementedintializeClass methods.

The Intialize method is called first in the parent class, and the Initialize method is called after the initialize method in the child class.

  1. In the parent classintializeClass method, not implemented in subclass.

The intialize class method is called twice in the parent class.

  1. The superclass, subclass, superclass classification, and subclass classification are implementedintializeClass methods.

The Intialize method in the parent class is called first, and the Initialize method in the parent class is called after the initialize method. (During the invocation of an instance method of a class or a class method, if the class has a method that implements the same name as the main class, the method in the class is called, but not for overridden reasons. See iOS Class Loading Process Analysis for more details.)

3. What is Runtime?

Runtime is a set of apis implemented in C, C++, and assembly. Runtime implements object-oriented functionality for the OC language, as well as Runtime functions that defer data type determination from compile time to Runtime (for example: The difference between extention and category), the OC code that you write in general, will eventually be converted into C code for Runtime, which is the person behind objective-C.

4. What is the nature of method calls?

The essence of the method is to send a message, and the process is as follows:

  1. Method, call objc_sendMsg (the bottom layer is assembly), find out if there is an IMP in cache_t corresponding to SEL, and call the IMP if there is, otherwise;

  2. Method slow lookup, call lookupImpOrForward, recursively use binary search algorithm to find yourself and the parent methodsList for sel imp, find add to the cache_t cache, call imp, otherwise;

  3. If there is a resolveInstanceMethod class method that overwrites NSObject and does the logical processing, then the call is made. A class method sends a resolveClassMethod message to this class. If there is a resolveClassMethod class method overriding NSObject and doing the logical processing, the call is made. Otherwise;

  4. Forward method is rapid, if class implements forwardingTargetForSelector instance methods, and returns a can handle this message object is invoked, otherwise;

  5. News fast forwarding, if realized methodSignatureForSelector and forwardInvocation methods in the class, and the related processing, will call methodSignatureForSelector method first, Then call the Fore Invocation method, otherwise;

6 Go through the method dynamic resolution again, if still no method implementation is found, it will throw an exception and crash.

5. SELWhat is?IMPWhat is? What is the relationship between the two?

SEL is the method number, compiled into memory during read_images.

IMP is the function implementation pointer, IMP is to find the process of the function.

SEL is equivalent to the title of a table of contents item in a book, IMP is equivalent to the page number of a table of contents item, search specific function is equivalent to you want to see the contents of an item in the book, according to the title of the item to find the page number, after finding the page number, turn to the specific page number to read the content.

6. Is it possible to add instance variables to the compiled class? Can you add instance variables to classes created at run time?

You cannot add an instance variable to a compiled class as long as the class has not been registered in memory. This is because the compiled instance variable is stored in ro. Once the compilation is complete, the memory structure is completely determined and cannot be modified. You can add properties as well as methods.

7. [self class][super class]The difference and the principle analysis

[self class] is calling objc_msgSend to send a message, the message receiver is self, the method number is class, and self is a hidden argument.

[super class] essentially calls objc_msgSendSuper2 to send a message, the receiver of the message is self again, the method number is class, and super is the keyword.

Write the following code:

@interface Person : NSObject

@end

@implementation Person

@end

@interface Student : Person

@end

@implementation Student

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"self_class = %@", [self class]);
        NSLog(@"super_class = %@", [super class]);
    }
    return self;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *s = [[Student alloc] init];
     }
    return 0;
}
Copy the code

Then compile this main.m file into CPP file. Init function in Student compiles the source code as follows:

What you can see is that all the methods called by the super keyword are actually calls to the objc_msgSendSuper function, but when you look at the runtime assembly code for the call to objc_msgSendSuper, you can see that this is actually calls to the objc_msgSendSuper2 function. Print self, class, and superclass as shown in the figure below:

Then add a symbolic breakpoint to objc_sendMsgSuper2, pass the breakpoint in init, go to the assembly code of objc_sendMsgSuper2, print the value in register X0 (the value of the objc_super * pointer passed in), Then print the values in the memory address (i.e., the values of receiver and super_class in objc_super), as shown in the figure below:

Super_class is a Person class, but it is actually a Student class. So sometimes the runtime code is different from the compile code.

We can see the comment in the function that the objc_msgSendSuper2 function will get the current search class, not its parent. The objc_msgSendSuper function is commented as follows:

In other words, objc_msgSendSuper will retrieve the superclass of the current search class. Objc_msgSendSuper is implemented in assembly. The arm64 assembly code is shown below:

This assembly code assigns the value of receiver in register X0 to p0, assigns the value of super_class in x0 to p16, and then jumps to L_objc_msgSendSuper2_body. Then look at the assembly code for objc_msgSendSuper2, as follows:

The value of class x0 is assigned to register X17, and the value of receiver is assigned to register X0. Then add the value of register x17 to the address of its SUPERCLASS (value 0x8) and assign it to register x17. Then take the value of the address of register x17 and assign it to register x16. And then we call AuthISASuper which does something, and then we go to assembly code at L_objc_msgSendSuper2_body, which is actually the same as objc_msgSendSuper call logic, Then take a look at the assembly code execution method lookup process at the CacheLookup. If the method is not found, it will be called, as shown below:

Based on the architecture of the real machine (iOS is small endian), the assembly code shown in the red box above will be executed:

  • Red box 1: willx16Register superclass (i.ePerson) is assigned tox15Register.
  • Red box 2: willx16The value of the register plus16Bytes (getPerson(objc_classType)cache) and then willcacheIn (i.ecache_tThe value of the first member variable in the structure_bucketsAndMaybeMaskAssigned top11Register.
  • Red box 3: Comparisonp11The first0Bit, if not for0, they performLLookupPreoptOtherwise, take out the assembly codep11The former48The value of bits (i.ebuckets) is assigned top10.
  • The red box 4:eorAssembly instructions are the ones that will(_cmd) ^ (p1 >> 7)The subsequent values are assigned top12.andAssembly instructions are top12 & (p11 >> 48)Is assigned top12(That is, calculation_cmdinbucketsIndex in), this has to do withcacheIs used to calculateselinbucketsThe hash function algorithm of the index in is consistent, as shown in the figure below:

The code in the red box above:

  • The red box 1:p12is_cmdHash function mapping inbucketsThe index value inbucketsIs pointing to the storage structurebucket_tThe member variable isSELAs well asIMP, a total of16Byte), so the index is shifted to the right16A plusbucketsThe first address will get the corresponding index locationbucket.
  • Red box 2: Displays the corresponding indexbucketIn theSEL,IMPtop17As well asp9Register, afterx13The value of the register minus16Size in bytes, to get the previous one forwardbucketAddress and then comparep9with_cmdThe value of the
  • Red box 3: Ifp9with_cmdEqual, cache hit, do thisimp.
  • Red box 4: Ifp9If the value is empty, the command is executed__objc_msgSend_uncachedAssembly code in.
  • Red box 5: IfbucketsFrom the calculated index position tobucketsWe got no matches to any of the initial addressessel“To break out of the loop.

After traversing the left part, we also traversed the remaining buckets on the right. The logic is consistent with the figure above, so we do not need to elaborate too much here. The code is as follows:

When the method quick lookup process is not found, the assembly code in __objc_msgSend_uncached is used, as shown below:

We then execute the assembly code in MethodTableLookup as shown in the figure below:

  • Red box 1: willx16The value of the register moves tox2Register, notice at this pointx16The register stores the superclassPersonThe address.
  • Red box 2: Jumps to code in the lookupImpOrForward· function that executes a slow lookup process.

So when lookupImpOrForward is called, its argument three CLS is the Person class, so it looks for instance methods init and class from the list of methods of the Person class, but the value of x0 is the instance object of the message receiver, Student. That is, the value of its parameter, inst.

  • Question1: Sinceobjc_msgSendSuper2Parameter one (objc_superType)super_classStudent. WhyStudentOf the classinitIn the methodself = [super init];This line of code doesn’t generate a loop call, right?

Because in objc_msgSendSuper2, we don’t look for methods from the member variable super_class (Student) in argument 1. Instead, we get super_class’s superclass (Person) and look for methods from the superclass (Person). If a quick lookup fails, lookupImpOrForward is called, with argument 1 inst being an instance of Student and argument 3 CLS being the Person superclass, so in Student init self = [super init]; This line of code does not generate a loop call, so write self = [self init]; The loop call will occur because [self init] will call objc_msgSend to send the message, and in objc_msgSend’s assembly code it will look up the init method from the Student method list.

  • Question 2: WhyStudentThe constructor that prints [self class] is equal to [super class]. Are allStudent.

Because [self class] sends a message using objc_msgSend, and the recipient of the message is the Student instance self, and the number of SEL is class, so when you look for a method, you look for a class method from the list of Student methods, When you call the class API at the bottom, you are actually calling the objc_opt_class function at run time. The code looks like this:

When you run this function, obj is actually the Student instance object, and when you get the Class of the Student instance object, you get the Student Class.

And [super class] is sending a message by calling objc_msgSendSuper2, and the recipient of the message is still the Student instance self, and the number of SEL is class. When you use objc_msgSendSuper2 to find the class method, We’re looking for a class method in the method list of Student’s superclass Person, and if there’s no class method in the method list of Person, we’re looking for a class method in the superclass of Person which is NSObject, When you call the class method in the NSObject method list, you’re actually executing the code for the object_getClass function in runtime’s underlying API, as shown in the figure below:

Where self is an instance of the Student class, and obj is an instance of the Student class when you call object_getClass, so isa is an instance of the Student class.

8. Analyze the following code and explain what NSLog functions print, and why?

#import <Foundation/Foundation. H > @interface Person: NSObject @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *nickname; @property (nonatomic, copy) NSString *hobby; - (void)sayHi; @end // person. m #import "person. h" @implementation Person - (void)sayHi {NSLog(@"%s --- %@", __func__, self.hobby); #import "viewcontroller.h" #import "person.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; p.hobby = @"play game"; p.name = @"xxx"; p.nickname = @"szx"; [p sayHi]; Class pCls = [Person class]; void *ptr = (void *)&pCls; [(__bridge id)ptr sayHi]; } @endCopy the code

The print result is as shown in the figure below:

First, the following memory structure diagram is drawn based on the memory addresses and stored values of each variable in the stack viewed during the run.

The green area in the structure diagram is the memory data diagram of the ViewDidLoad function call stack. The top pointer is high and the bottom pointer is low. During the execution of the ViewDidLoad call, The two hidden arguments in the ViewDidLoad method self and _cmd (both 8 bytes) are pushed from high to low, and an objc_super struct variable is created in the middle. This variable is created by calling the line [super ViewDidLoad], We’ve already looked at what super does. It calls objc_sendMsgSuper2 to send a message. It takes two arguments, one is a pointer to the objc_super structure, So you need to create a local variable of the objc_super structure type (recevier stores the value of self, super_class stores the value of ViewController, which is the value of the self pointer to the address isa), but you might be wondering, Don’t you need to create a local variable to store the second SEL parameter? The second SEL argument is created by calling sel_registerName(const char * _Nonnull STR) (the argument is a string constant stored in the constant area). The return value is not received by a local variable. Instead, the call to sel_registerName is passed directly to objc_sendMsgSuper2 as argument 2, and only its arguments and local variables are pushed on the stack during the call. Therefore, the following local variables p, pCls, and PTR are pushed from the higher address to the lower address.

Struct Person_IMPL (struct Person_IMPL, struct Person_IMPL); The “p” pointer will store the isa, name, nickname, and hobby variables in the heap, as shown in the pale blue image above. Therefore, when the p object calls its sayHi instance method to access the value of its hobby variable, So it calls objc_sendMsg to send a message, and the recipient is p, and the method number is hobby, and it takes the address of its class Person (that’s isa) from p, and then it does a method lookup, looks up the hobby method in the list of methods in Person, and then it calls, The hobby method actually accesses the value of the hobby member variable in the p object by a memory shift. The hobby method is obtained by adding 24 bytes to the value of the p pointer. Therefore, when calling sayHi through the PTR pointer, the PTR pointer is treated as an instance object. Again, the objc_sendMsg function is called to send a message, and the receiver of the message is the PTR pointer, and the method number is sayHi, and then the objc_sendMsg function (which is actually written in assembly code) treats the PTR as an instance object, that is, as a pointer to a class object, In this case, the PTR pointer is pointing to the address of a local variable, pCls, which happens to be a Person object, so we look up sayHi from the list of Person methods, we call sayHi, and then we call objc_sendMsg to send a message, The receiver is still PTR, and the method number is hobby, so when you call the IMP implementation of the hobby method, you will still shift the value of PTR to try to get the value of its member variable hobby, Instead, you get the address (0x7ffeeed750e0 + 0x18) 0x7FFeeed750F8, which is the super_class (ViewController class), and when you print the value from that address, you print out the ViewController string.

9. MethodSwizzling pit, check the following iOS project part of the code, compile and run the program, what will happen, how to solve the problem?

// person. h file code @interface Person: NSObject - (void)personInstanceMethod; #import "person. h" @implementation Person - (void)personInstanceMethod {NSLog(@"Person" instance method: %s", __func__); #import "Person. H "@interface Student: Person @end // study. m file code #import "study. h" @implementation study. h file code #import" study. h" @implementation study. h file code #import "study. h" @implementation study. h file code @interface Student (CA) - (void)categorySwizzlingMethod; @end // load @implementation Student (CA) + (void)load @end // load @implementation Student (CA) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [RuntimeTool methodSwizzlingWithClass:[self class] oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(categorySwizzlingMethod)]; }); } - (void)categorySwizzlingMethod { [self categorySwizzlingMethod]; NSLog(@"Student class instance method: %s", __func__); #import <Foundation/Foundation. H > @interface RuntimeTool: NSObject /// swap method /// @param CLS swap object /// / @param oriSEL original method number /// @param swizzledSEL swap method number + (void)methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL; #import <objc/message.h> @implementation RuntimeTool + (void)methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL { if (! CLS) {NSLog(@" the passed exchange class cannot be empty!" ); return; } Method m1 = class_getInstanceMethod(cls, oriSEL); Method m2 = class_getInstanceMethod(cls, swizzledSEL); method_exchangeImplementations(m1, m2); #import "viewcontroller.h" #import "person.h" #import "student.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Student *s = [[Student alloc] init]; [s personInstanceMethod]; Person *p = [[Person alloc] init]; [p personInstanceMethod]; } @endCopy the code

First of all, the Student object s call personInstanceMethod method print information is normal, but the Person object p call PersonInstanMethod method complains, as shown in the figure below:

The reason for this is that you call RuntimeTool in the Load method of the Student class and you pass oriSEL through the runtime API to the tool class methodSwizzlingWithClass The m1 obtained by class_getInstanceMethod is actually a method from the list of methods in the parent class Person, because the Student class does not override the parent personInstanceMethod method. Class_getInstanceMethod is going to look for the personInstanceMethod method in the Student class, and if you can’t find it, you’re going to recursively look for the personInstanceMethod method in the parent class, and if you do, you’re going to return it, So when I call the Runtime API method_exchangeImplementations, What you’re actually swapping is the implementation of personInstanceMethod in the parent class with the implementation of the methodSwizzlingWithClass method in the Student class, So when the P object calls its own personInstanceMethod method it calls the implementation of the methodSwizzlingWithClass method that executes the Student class, [self categorysvwizzlingmethod]; [self categorysvwizzlingmethod]; In this line of code, self is actually a Person object p, so call objc_msgSend to send a message to p with a categorySwizzlingMethod number, Person class: categorySwizzlingMethod: Person class: categorySwizzlingMethod: Person class: Student; Person class categorySwizzlingMethod is not found in the list of methods in the Person class, nor in the list of methods in its parent class.

So how do you solve this problem? What’s the starting point? First of all, we should consider where to change the code. The root of the problem is that we wanted to change the Student class, but we changed the IMP pointing to the personInstanceMethod method of the parent class Person. The result is that, The Person class method personInstanceMethod, which is used normally, now reports an error, and other subclasses of the Person class call the personInstanceMethod method, and developers may be confused when they encounter these errors. If we do not know the cause of the error, it will be difficult to check. Therefore, we should ensure that when swapping methods in the Student category, both methods implemented by the IMP are in the Student method list. Therefore, after obtaining M1 and M2, Try adding a method named oriSEL to the Student class by calling runtimeAPI class_addMethod. If this is successful, then there is no m1 method in the Student class. M1 method may be a method in its parent class. Method m1 (swizzlingwithClass); method M1 (swizzlingwithClass); method M1 (swizzlingwithClass); method M1 (swizzlingwithClass);

+ (void)methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL { if (! CLS) {NSLog(@" the passed exchange class cannot be empty!" ); return; } Method m1 = class_getInstanceMethod(cls, oriSEL); Method m2 = class_getInstanceMethod(cls, swizzledSEL); bool isAddMethodSuccess = class_addMethod(cls, oriSEL, method_getImplementation(m2), method_getTypeEncoding(m2)); if (isAddMethodSuccess) { class_replaceMethod(cls, swizzledSEL, method_getImplementation(m1), method_getTypeEncoding(m1)); } else { method_exchangeImplementations(m1, m2); }}Copy the code

If the m1 method is null, the swizlledSEL IMP will be replaced with null, and the call will be incorrect. For example, we commented out the personInstanceMethod method declaration and implementation in the Person class. Write the following code in ViewDidLoad of the ViewController:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *s = [[Student alloc] init];

    [s categorySwizzlingMethod];
    
}
Copy the code

When compiling and running the program, an error like the following appears:

This error is caused by the Runtime API class_replaceMethod function, which looks for SEL in a list of methods passed to it. If no Method is found, the IMP is returned. If no Method is found, the IMP is replaced by the IMP. If no Method is found, the IMP is replaced by the IMP. Creates a Method list newList of length 1 for method_list_t, and sets the values of SEL, IMP (whether the IMP is empty or not), and types to the first Method in the newList. The list newList is then sorted and inserted first into the methods of class REw. In methodSwizzlingWithClass, m1 is null, so if class_replaceMethod is called, there is a categorywizzlingmethod in the Student class. However, the passed IMP is null, so there is no change in the categorySwizzlingMethod’s IMP reference, thus creating a dead recursion.

So, in methodSwizzlingWithClass, you should also use a Method for M1. If m1 is null, add a Method for IMP (oriSEL, m2), and then add a default IMP for M1. The advantage of this approach is not only that the above error can be handled, but also that the developer will not be able to call orlSEL elsewhere without the error that the method cannot be found. The improvement of the code is as follows:

+ (void)methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL { if (! CLS) {NSLog(@" the passed exchange class cannot be empty!" ); return; } Method m1 = class_getInstanceMethod(cls, oriSEL); Method m2 = class_getInstanceMethod(cls, swizzledSEL); if(! M1) {//m1 is empty // add oriSEL number method to CLS, IMP is m2 IMP, outside can call oriSEL number method, and execute IMP m2. class_addMethod(cls, oriSEL, method_getImplementation(m2), method_getTypeEncoding(m2)); // Add a default IMP to M1 so that IT can successfully swap IMP with M2, Method_setImplementation (m1, imp_implementationWithBlock(^(id self, SEL _cmd)){NSLog(@" the original method of the transaction is empty!" ); })); } // M1 is not null, but it does not guarantee that m1 is in the CLS method list, so you need to add oriSEL to the CLS method list. If you add oriSEL to the CLS method list, you need to add oriSEL to the CLS method list. Bool isAddMethodSuccess = class_addMethod(CLS, oriSEL, method_getImplementation(m2), method_getTypeEncoding(m2)); If (isAddMethodSuccess) {// If m1 is not in the CLS method list, replace the implementation of the method that calls swizzledSEL with the implementation of the m1 method. class_replaceMethod(cls, swizzledSEL, method_getImplementation(m1), method_getTypeEncoding(m1)); } else {// If M1 is in CLS method list, then swap M1 with IMP Implementations(m1, m2); }}Copy the code