[iOS] Call to category override method

Keywords: xcode SDK

Two classifications of a class (one in my own implementation and one in a third-party SDK) appeared in the project two days ago, and a method with the same method name was implemented at the same time. At that time, there was a question, which classification method was invoked when it was actually called?

1. Catgory overrides the main class method

First, look at this point, and I believe most people already know that if a class's classification overrides its method, then this method of the class will fail, the override of the classification will work, and Xcode will warn you when the classification overrides that the classification implementation will replace the main class implementation

So how does the original method fail and the classification method work?

To understand this, first look at class initialization, oc is a dynamic language, based on runtime, the same class initialization is dynamic. The root class NSObject's + load and + initilize methods are used for class initialization, we will focus on the + load method here:

The + load method is called when a class or class is added to the Objective-C runtime, and implementing this method allows us to perform some class-related behavior when the class is loaded.The subclass's + load method is executed after all its parent's + load methods are executed, while the classified + load method is executed after its main class's + load methods are executed.However, the calling order of the + load method between different classes is uncertain.

That's why, since the loading order is parent+load, then subclass+load, then classification+load, if classification overrides subclass methods: first subclass+load, add methods to the method list methodLists of classes, then classification+load, add overridden methods to the method list, but there are a few questions here:
1. Will there be two SEL s with the same method in the method list methodLists?
2. If so, what is the order of the two methods in the method list?(Order determines which is called)

Here's how to verify:

#import "TestCategory.h"
/*Main class implementation*/
@implementation TestCategory
- (void)newMethod {
    NSLog(@"Main Class");
}
@end

#import "TestCategory+add.h"
/*Classification One Implementation*/
@implementation TestCategory (add)
- (void)newMethod {
    NSLog(@"Category One");
}
@end


#import <objc/runtime.h>
#import "TestCategory.h"
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    id LenderClass = objc_getClass("TestCategory");
    unsigned int outCount, i;
    //Get a list of instance methods
    Method *methodList = class_copyMethodList(LenderClass, &outCount);
    for (i=0; i<outCount; i++) {
        Method method = methodList[i];
        NSLog(@"instanceMethod: %@", NSStringFromSelector(method_getName(method)));
    }

    //Call once
    TestCategory *tCategory = [[TestCategory alloc]init];
    [tCategory newMethod];
}

Look at the output:

2017-07-19 21:38:13.593 TestRuntimeProperty[27827:1306334] instanceMethod: newMethod
2017-07-19 21:38:13.593 TestRuntimeProperty[27827:1306334] instanceMethod: newMethod
2017-07-19 21:38:13.594 TestRuntimeProperty[27827:1306334] Category One

So
1. There will be two methods in the method list that have the same SEL.
2. When actually invoked, the post-added method is invoked, that is, the post-added method is at the top of this array in the method list methodLists

The answer is already clear: the method of the latter + load class is added to the method list, and then to the method list by inserting the top addition, that is, [methodLists insertObject:category_method atIndex:0]; so when objc_msgSend traverses the method list to find the IMP corresponding to the SEL, it will find the one overridden by the classification first and call execution.Then add it to the list of caches so that the main class method implementation never calls to it.

(Note: The structure definitions of methodLists and methods can be seen in my last article- Runtime Interpretation)

2. Multiple categories implement the same method

But what is the order of execution if multiple classifications override the same method at the same time?

The answer is: For multiple classifications rewriting the same method at the same time, Xcode is compiled from top to bottom at run time in order within buildPhases->Compile Sources, which is obviously like subclasses and classifications, then the post-load of compilation is added to the method list, so the post-compiled classification is placed at the top of the method list and executed when invoked.

Add code to verify:

#import "TestCategory+addAgain.h"
/*Classification Two Implementation*/
@implementation TestCategory (addAgain)
- (void)newMethod {
    NSLog(@"Classification II");
}
@end

Look at the output:

2017-07-19 22:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod: newMethod
2017-07-19 22:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod: newMethod
2017-07-19 21:18:13.593 TestRuntimeProperty[28385:1331972] instanceMethod: newMethod
2017-07-19 22:18:13.594 TestRuntimeProperty[28385:1331972] Category One

The result output is still in Category One, which means that "TestCategory+add.h" is lower in order in buildPhases->Compile Sources, so look at buildPhases:

In summary, the order in which classes are loaded determines the order in which methods are added. When called, the methods added after are found first, so the method implementation of the classes loaded after is always called.

Posted by cody44 on Mon, 10 Jun 2019 13:26:17 -0700