Class loading principle: The loading principle of iOS class; the loading principle of iOS class; the loading principle of iOS class; the loading principle of iOS class; the loading principle of classification; the class extension; the associated object is introduced

ro, rw, rwe

So before we move on to class loading, let’s take a look at the concept of what ro, rw, and RWE are.

Ro: When an app uses a class, it needs to read the class information from the app binary file on disk. The binary file stores the class metaclass, parent class, flags and method cache. The additional information (name, method, protocol, instance variables, etc.) is stored in class_ro_T. Class_ro_t ro, read only. Reading a class from disk to memory is an assignment to ro. Since RO is read-only and does not change after loading into memory, it is also called clean memory.

Rw: class_rw_t rw for short, read write, used to read and write programs. Drity Memory Memory that changed while the process was running. As soon as the class is used, the runtime allocates an extra memory, which becomes DRity Memory. In practice, however, the class usage is only 10%, which is a waste of memory in the RW, so Apple put the methods, protocols, and instance variables in the RW in class_rw_ext_t.

Rwe: class_rw_ext_t rWE for short, read write ext, used to store information about the class’s methods, protocols, and instance variables at runtime.

I recommend you to watch this video WWDC20. I believe you will have a more detailed understanding of RO, RW and RWE after watching this video.

AttachCategories Backward thinking

Now that we know about ro, rw and rWE, let’s first look at where the rew is assigned.

In the attachCategories method of the source code we see that auto Rwe = CLS ->data()->extAllocIfNeeded(), so it is necessary to call extAllocIfNeeded if rWE is to be assigned.

A search for extAllocIfNeeded shows that extAllocIfNeeded functions are called when adding categories, protocols, attributes, etc. This also validates the WWDC video that changes the RWE only when you add a method to a class when the class is loaded or when you call the runtime API to dynamically add a method or property. Because here we’re exploring the loading of categories, say we’re looking at the attachCategories function. Since we don’t know when the category was loaded, we’ll look back to see where attachCategories are called.

A search shows that attachCategories were called at load_categories_NOLock and attachToClass.

Then we searched attachToClass and found that this function was called in three places, but all from within the methodizeClass function. We see that the code logic goes to if (Previously) {} only if previously has a value, and previously is a function argument, so we search for methodizeClass.

MethodizeClass is called in realizeClassWithoutSwift, so continue to search realizeClassWithoutSwift.

After searching, you can see that the previously passed message is nil when you call realizeClassWithoutSwift, which is an alternate argument, so it doesn’t go inside if (Previously) {}. So the attachCategories call flows as follows.

  1. load_categories_nolock -> attachCategories
  2. realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories

5 cases of classification and main class loading

From the previous analysis, we already know that the loading of a class can be divided into two cases: lazy loading and non-lazy loading. Is the loading of a class also related to the load method? Here we will explore several cases.

1. Both classes and classifications implement the load method

@interface LGPerson : NSObject { int a; } @property (nonatomic, copy) NSString *name; - (void)saySomething; + (void)sayHappy; @end #import "LGPerson.h" #import <objc/message.h> @implementation LGPerson + (void)load{} - (void)saySomething{ NSLog(@"%s",__func__); } + (void)sayHappy{ NSLog(@"LGPerson say : Happy!!!" ); } @endCopy the code
#import "LGPerson.h" @interface LGPerson (LGA) @property (nonatomic, copy) NSString *cateA_name; - (void)saySomething; - (void)cateA_instanceMethod1; + (void)cateA_classMethod1; @end #import "LGPerson+LGA.h" @implementation LGPerson (LGA) + (void)load{} - (void)saySomething{ NSLog(@"%s",__func__);  } - (void)cateA_instanceMethod1{ NSLog(@"%s",__func__); } + (void)cateA_classMethod1{ NSLog(@"%s",__func__); } @endCopy the code

First of all, both classes and categories implement the load method, and then we intercept it in attachCategories to take a look at the overall load process.

Through breakpoint interception, we can see that the load process when both classes and classes implement the load method is as follows:

_read_images (non-lazy load class) -> realizeClassWithoutSwift -> load_categories_nolock -> attachToClass -> attachCategories

When we go to realizeClassWithoutSwift we print ro, and we see that the number of methods in $3 is 3, all of which are methods of the main class, which means that the class method is not loaded into ro.

In the load_categories_nolock function we print that count is the number of categories, and we print cat to see the category structure and category name.

Then in the for (uint32_t I = 0; i < cats_count; I++) and you can see that mlist is exactly the two methods in the corresponding category LGPerson (LGA), and the logic of the code execution is to store the mlist address in the 63rd bit of the mlists.

And then we look down.

So what we can see here is that mlists + ATTACH_BUFSIZ -mcount is a second-level pointer, and prepareMethodLists has been analyzed before, and it’s going to sort the methods. Then we go to the attachLists function.

Array ()->lists[1] stores the main class method list, array()->lists[0] stores the classification method list.

This is a little different when there are multiple categories, as the attachLists function goes to the if (hasArray()) condition.

2. Classification implementationloadMethod, which the main class does not implementloadmethods

@interface LGPerson : NSObject { int a; } @property (nonatomic, copy) NSString *name; - (void)saySomething; + (void)sayHappy; @end #import "LGPerson.h" #import <objc/message.h> @implementation LGPerson //+ (void)load{} - (void)saySomething{ NSLog(@"%s",__func__); } + (void)sayHappy{ NSLog(@"LGPerson say : Happy!!!" ); } @endCopy the code
#import "LGPerson.h" @interface LGPerson (LGA) @property (nonatomic, copy) NSString *cateA_name; - (void)saySomething; - (void)cateA_instanceMethod1; + (void)cateA_classMethod1; @end #import "LGPerson+LGA.h" @implementation LGPerson (LGA) + (void)load{} - (void)saySomething{ NSLog(@"%s",__func__);  } - (void)cateA_instanceMethod1{ NSLog(@"%s",__func__); } + (void)cateA_classMethod1{ NSLog(@"%s",__func__); } @endCopy the code

When the main class does not implement the load method and the classification does not implement the load method, the loading process is as follows, which is similar to the case where the main class implements the load method and the classification does not implement the load method, and the attachCategories method is not used.

_read_images (non-lazy load class) -> realizeClassWithoutSwift -> load_categories_nolock -> attachToClass

One problem you can see here is that the main class will go through the non-lazy loading process without implementing the load method, because the class implements the load method. But there is no attachCategories method, so when is the classification method loaded into the list of methods?

And you can see from the breakpoint output here in the realizeClassWithoutSwift method that the method count in method_list_t is 5, which is just the number of methods in the main class plus the number of methods in the class, P $3.get(1).big(); p $3.get(1).big(); p $3.get(1).big(); The classified method data is available at this point through data().

3. Classification is not implementedloadMethod, the main class implementationloadmethods

@interface LGPerson : NSObject { int a; } @property (nonatomic, copy) NSString *name; - (void)saySomething; + (void)sayHappy; @end #import "LGPerson.h" #import <objc/message.h> @implementation LGPerson + (void)load{} - (void)saySomething{ NSLog(@"%s",__func__); } + (void)sayHappy{ NSLog(@"LGPerson say : Happy!!!" ); } @endCopy the code
#import "LGPerson.h"

@interface LGPerson (LGA)

@property (nonatomic, copy) NSString *cateA_name;

- (void)saySomething;

- (void)cateA_instanceMethod1;

+ (void)cateA_classMethod1;

@end

#import "LGPerson+LGA.h"

@implementation LGPerson (LGA)

//+ (void)load{}


- (void)saySomething{
    NSLog(@"%s",__func__);
}

- (void)cateA_instanceMethod1{
    NSLog(@"%s",__func__);
}

+ (void)cateA_classMethod1{
    NSLog(@"%s",__func__);
}

@end
Copy the code

When the class does not implement the load method and the main class implements the load method, you can see that the loading process is as follows. When the class and the class implement the load method, the attachCategories method is not used.

_read_images (non-lazy load class) -> realizeClassWithoutSwift -> load_categories_nolock -> attachToClass

So when the class doesn’t implement the load method, and the main class implements the load method in the realizeClassWithoutSwift method printed here you can see it’s similar to when the main class doesn’t implement the load method, and the class implements the load method, This is where data() is used to get the classified method data.

4. Neither classification nor main class is implementedloadmethods

@interface LGPerson : NSObject { int a; } @property (nonatomic, copy) NSString *name; - (void)saySomething; + (void)sayHappy; @end #import "LGPerson.h" #import <objc/message.h> @implementation LGPerson //+ (void)load{} - (void)saySomething{ NSLog(@"%s",__func__); } + (void)sayHappy{ NSLog(@"LGPerson say : Happy!!!" ); } @endCopy the code
#import "LGPerson.h"

@interface LGPerson (LGA)

@property (nonatomic, copy) NSString *cateA_name;

- (void)saySomething;

- (void)cateA_instanceMethod1;

+ (void)cateA_classMethod1;

@end

#import "LGPerson+LGA.h"

@implementation LGPerson (LGA)

//+ (void)load{}


- (void)saySomething{
    NSLog(@"%s",__func__);
}

- (void)cateA_instanceMethod1{
    NSLog(@"%s",__func__);
}

+ (void)cateA_classMethod1{
    NSLog(@"%s",__func__);
}

@end
Copy the code

When neither the main class nor the classification implements the load method you can see that it goes directly to the main function.

When neither the main class nor the classification implements the load method, you can see that class initialization is delayed until the first message is received by the current class, but both the main class and the classification method are loaded using data() in the realizeClassWithoutSwift function. At this point, it’s loaded.

5. When the main class implementsloadMethod, classification is not fully implementedloadmethods

@interface LGPerson : NSObject { int a; } @property (nonatomic, copy) NSString *name; - (void)saySomething; + (void)sayHappy; @end @implementation LGPerson + (void)load{} - (void)saySomething{ NSLog(@"%s",__func__); } + (void)sayHappy{ NSLog(@"LGPerson say : Happy!!!" ); } @end @interface LGPerson (LGA) @property (nonatomic, copy) NSString *cateA_name; - (void)saySomething; - (void)cateA_instanceMethod1; + (void)cateA_classMethod1; @end #import "LGPerson+LGA.h" @implementation LGPerson (LGA) + (void)load{} - (void)saySomething{ NSLog(@"%s",__func__);  } - (void)cateA_instanceMethod1{ NSLog(@"%s",__func__); } + (void)cateA_classMethod1{ NSLog(@"%s",__func__); } @end @interface LGPerson (LGB) - (void)saySomethingB; @end @implementation LGPerson (LGB) //+ (void)load{} - (void)saySomethingB { NSLog(@"%s",__func__); } @endCopy the code

A special case here is when the main class LGPerson implements the load method, but the class only LGA implements the load method. You can see that the load_categories_nolock method applies, where count happens to be the number of classes. This is where we loop through the attachCategories function. As you can see above, normally we can read the method data of the class and main class through data(). In fact, the MachO file loads the data directly, but the load method breaks this order, so we should be careful to use the load method.