Summary of basic principles of iOS – The nature of categories

The interview questions

  1. How to implement a Category, and why you can only add methods to a Category and not properties.
  2. Is there a load method in Category? When is the load method called? Can the load method inherit?
  3. The difference between Load and initialize, and the order in which they are called when a category is overwritten.

The essence of the Category

We’ll start by writing a simple piece of code that will base the rest of our analysis on.

Presen class / / Presen. H#import <Foundation/Foundation.h>
@interface Preson : NSObject
{
    int _age;
}
- (void)run;
@end

// Presen.m
#import "Preson.h"
@implementation Preson
- (void)run
{
    NSLog(@"Person - run"); } @end Presen extends 1 // Presen+ test.h#import "Preson.h"
@interface Preson (Test) <NSCopying>
- (void)test;
+ (void)abc;
@property (assign, nonatomic) int age;
- (void)setAge:(int)age;
- (int)age;
@end

// Presen+Test.m
#import "Preson+Test.h"
@implementation Preson (Test)
- (void)test
{
}

+ (void)abc
{
}
- (void)setAge:(int)age
{
}
- (int)age
{
    return10; } @end Presen category 2 // Preson+ test2.h#import "Preson.h"
@interface Preson (Test2)
@end

// Preson+Test2.m
#import "Preson+Test2.h"
@implementation Preson (Test2)
- (void)run
{
    NSLog(@"Person (Test2) - run");
}
@end
Copy the code

When P calls run, the isa pointer of the class object finds the ISA pointer of the class object, and then looks for the object method in the class object. If not, the superclass pointer of the class object finds the superclass object. Then look for the run method.

So when calling a classified method, are the steps the same as when calling an object method? The object methods in the classification are still stored in the class object, in the same place as the object methods, so the call procedure is the same as calling object methods. If it is a class method, it is also stored in a metaclass object. So how is the classification method stored in the class object, we will look at the underlying structure of the classification through the source code.

The underlying structure of classification

How to verify the above questions? The category_T structure can be found by looking at the source of the classification.

struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; Struct method_list_t *classMethods; Struct protocol_t *protocols; Struct property_list_t *instanceProperties; // attributes // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Copy the code

From the source basic can see that we usually use categroy way, object method, class method, protocol, and attribute can find the corresponding storage way. And we find that there is no member variable in the classification structure, so it is not allowed to add member variable in the classification. The attributes added to the classification do not help us automatically generate member variables, only get set method declarations, which we need to implement ourselves.

Through the source code we found that classification methods, protocols, attributes and so on do seem to be stored in the Categroy structure, so how is it stored in the class object? Let’s look at the underlying internal methods and see how they work. First, we convert the Preson+ test. m file from the command line to a c++ file to see the compilation process.

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Preson+Test.m
Copy the code

The _category_t structure contains the class name, object method list, class method list, protocol list, and attribute list.

Next, we see a structure of type _method_list_t, as shown in the figure below

The struct _OBJC_$_CATEGORY_INSTANCE_METHODS_Preson_$_Test is named INSTANCE_METHODS and should be assigned values one by one. We can see that the structure stores the memory used by methods, the number of methods, and the list of methods. And from the figure above, we can find the corresponding object methods, test, setAge and age

Next we find the same class method structure of type _method_list_t, as shown in the figure below

_OBJC_$CATEGORY_class_methods_preson_ $_Test is the same as the object method list structure.

Next is the list of protocol methods

Store protocol methods in the _method_list_t structure. It is then stored in the _OBJC_CATEGORY_PROTOCOLS_$_Preson_$_Test through the _PROTOCOL_T structure, corresponding to the _PROTOCOL_LIST_t structure. Protocol_count is the number of protocols and the _PROTOCOL_T structure that stores protocol methods.

Finally, we can see the property list

_OBJC_$_PROP_LIST_Preson_$_Test

Finally, we can see that the _OBJC_$_CATEGORY_Preson_$_Test structure is defined and assigned to each of the structures we analyzed above. Let’s compare the two images.

The two graphs correspond one by one, and we see the OBJC_CLASS_$_Preson structure that defines type _class_T, and finally the CLS pointer to _OBJC_$_CATEGORY_Preson_$_Test points to the address of the OBJC_CLASS_$_Preson structure. We can see here that the CLS pointer should point to the address of the main class object.

Through the above analysis we found that. The classified source code does store our defined object methods, class methods, attributes and so on in the catagory_t structure. Let’s go back to the Runtime source to see how catagory_t stores methods, properties, protocols, and so on in class objects.

First comes the Runtime initialization function

Then we go to the &map_images reading module (images stands for module), go to the map_images_NOLock function to find the _read_images function, and in the _read_images function we find the classification code

From the above code we can see that this code is used to find whether there is a classification. After the _getObjc2CategoryList function is used to obtain the classification list, the method, protocol, attribute, etc. You can see that remethodizeClass(CLS) is eventually called; Function. We come to remethodizeClass(CLS); Function internal view.

Let’s go inside the attachCategories function.

The above source code can be seen, first according to the method list, attribute list, protocol list, MALloc allocation of memory, according to how many categories and how much memory each piece of method needs to allocate the corresponding memory address. Then from the classification array, store the classification methods, attributes and protocols in the three arrays into the corresponding mList, Proplists and Protolosts arrays, which contain all the classification methods, attributes and protocols. Class_rw_t contains the methods, attributes, and protocols of the class_rw_t class object. The rW structure is obtained by the data() method of the class object. So the RW holds the data in this type of object. After that, the attachList function of the method list, attribute list and protocol list was called by RW respectively, and all the classified methods, attributes and protocol list arrays were passed in. We can roughly guess that the classification was combined with the corresponding object methods, attributes and protocols within the attachList method.

Let’s look inside the attachLists function.

The above source code has two important arrays array()-> Lists: the class object’s original method list, property list, protocol list. AddedLists: Pass a list of methods, a list of attributes, a list of protocols for all categories.

The two most important methods of attachLists function are memmove and memcpy memory copy. So let’s look at these two functions separately

// memmove: memory move. /* __dst: the destination of the moved memory * __src: the first address of the moved memory * __len: */ void *memmove(void *__dst, const void *__src, size_t __len) */ void *memmove(void *__dst, const void *__src, size_t __len); // memcpy: memory copy. /* __dst: the destination of the copied memory * __src: the first address of the copied memory * __n: */ void *memcpy(void *__dst, const void *__src, size_t __n) */ void *memcpy(void *__dst, const void *__src, size_t __n);Copy the code

Here’s how memory changes after memmove and memcpy.

After the memmove method, the memory changes to

// array()-> Lists original method, attribute, protocol list array // addedCount Category array length // oldCount * sizeof(array()-> Lists [0]) Space occupied by the original array memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));Copy the code

After the memmove method, we find that although the list of methods, attributes and protocols of this class will move backward respectively, the pointer of the corresponding array of this class still points to the original location.

Memory changes after the memcpy method

// array()-> Lists original method, attribute, protocol list array // addedCount * sizeof(array()-> Lists [0]) The space occupied by the original array memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));Copy the code

We find that the pointer doesn’t change, it points to the beginning all the way through. And after memmove and memcpy methods, the list of classified methods, properties, and protocols is placed in front of the list of methods, properties, and protocols originally stored in the class object.

So why append the list of class methods to the original object method? The purpose of this is to ensure that the class method is called first, we know that when the class overrides the method of this class, it overrides the method of this class. In fact, after the above analysis we know that the nature is not overwrite, but priority call. The methods of this class are still in memory. We can see this by printing all method names for all classes

- (void)printMethodNamesOfClass:(Class)cls { unsigned int count; Method *methodList = class_copyMethodList(CLS, &count); // Store method name NSMutableString *methodNames = [NSMutableString String]; // Iterate over all the methodsfor(int i = 0; i < count; Method Method = methodList[I]; NSString *methodName = NSStringFromSelector(method_getName(method)); [methodNames appendString:methodName]; [methodNames appendString:@","]; } // Free (methodList); // Prints the method name NSLog(@"% @ - % @", cls, methodNames);
}
- (void)viewDidLoad {
    [super viewDidLoad];    
    Preson *p = [[Preson alloc] init];
    [p run];
    [self printMethodNamesOfClass:[Preson class]];
}
Copy the code

As you can see from the print below, the Run method in Test2 is called and two run methods are stored in the Person class.

Conclusion:

Q: How does Category work and why only methods can be added to categories but not attributes?

A: Categorization works by placing the category method, attribute, and protocol data in the category_T structure, and then copying the list of methods in the category_T structure into the method list of the class object. Categories can add attributes, but they do not automatically generate member variables and set/ GET methods. There are no member variables in the CATEGORY_t structure. We know from our previous analysis of the object that member variables are stored in the instance object and are determined at compile time. Categories are loaded at run time. Then we cannot add the member variables of the class to the structure of the instance object at runtime. Therefore, no member variables can be added to a classification.

The load and initialize

The load method is called when the program starts, when the class information is loaded. Call order look at the source code.

Let’s verify this with code: we add Student inheriting Presen, and add Student+Test, override the +load method, and do nothing else by printing

We then add the Initialize method for Preson, Student, and Student+Test. We know that initialize is called the first time a class receives a message, which is equivalent to the initialize method being called the first time a class is used. The initialize method of the parent class is guaranteed to be called before the initialize method of the subclass is called. If initialize has been called before, the initialize method will not be called again. The class method is called first when the class overrides the Initialize method. However, the load method is not overridden, so let’s take a look at the initialize source code.

In the figure above, initialize is called by the message sending mechanism, which uses the ISA pointer to find the corresponding method and implementation. Therefore, the implementation of the classification method will be called first.

Let’s take a look at the load method call source

conclusion

Q: Is there a load method in a Category? When is the load method called? Can the load method inherit?

A: The Category has a load method, which is called when the program starts loading the class information. The load method can be inherited. The load method of the subclass is called before the load method of the subclass is called

Q: The difference between Load and initialize, and the order in which they are called when a category is overwritten.

Load is called from the address of the function, initialize is called from objc_msgSend: Load is called when the Runtime loads a class or class (only once). Initialize is called when the class first receives a message. Each class is initialized only once (the parent class’s initialize method may be called multiple times).

Call order: the load method of the class is called first, and the load method is called first when the class is compiled. The load method of the parent class is called before calling load. The load method in a class does not override the load method of the class. The first compiled class calls the load method first. Initialize first initializes the parent class and then the child class. If the subclass does not implement +initialize, the parent class’s +initialize is called (so the parent class’s +initialize may be called multiple times), and if the class implements +initialize, the class’s +initialize call is overridden.


This article is the summary of the underlying principle of learning, if there is any wrong place please correct, welcome everyone to exchange learning XX_CC.