This chapter content
- Nature of classification
- Rwe opens up assignment
- Classification loading process
- Five case analysis classes and classifications to achieve the load method
This ZhangMu
Know the underlying structure of the classification, and know the classification loading process.
Nature of classification
What is the prototype of the classification at the bottom of OC? In fact, it’s easy to understand, if you think about the classification if it’s not implemented at the bottom what is the storage? Therefore, it is necessary to study the bottom layer, where classification is realized as structure. We can look at it by restoring the CXX file, or even explore the objC source code. But for a more intuitive view I recommend the first approach, the Clang restore approach, and of course we’ll show the objC source code
First look at the source file, then look at the following two ways to compare
Clang reduction
- View the classification essential structure
category_t
- View the implementation of CXX file classification. You might wonder why name is clang as Person. You don’t have to wonder, because we’re a clang restore, and clang is a compile-time LLVM thing that gives anonymity at this point, not a real name.
Classification objC source code
You can see that it’s basically the same as the clang restore, but the class attributes THAT I’ve highlighted will certainly make some people wonder, what are class attributes, OC and class attributes? There is, but what does it mean for class attributes? You can think of it as calling person. name, but the class property is the same as any other language where the call permission is for that class, but the actual memory is global (the instance property is implemented as ivar+setter+getter, whereas the class property is the global variable assigned to the called setter+getter). If you want to know, you can see my supplement.
Supplementary class attribute
The launch of rwe,
In fact, there is nothing to see here, but it is singled out to solve the problems left over from the previous chapter. So let’s see what the structure of RWE looks like. It’s very simple. There are only a few member variables, so if we want to find the rWE opener, we need to know how to initialize it first
Rwe initialization
Class_rw_ext_t *extAllocIfNeeded() ¶ We know that rW contains rWE. If we look in rW, there is a method class_rw_ext_t *extAllocIfNeeded() that deinitializes rWE.
Conditions for RWE initialization
Now that we’ve found its initialization method, we can search globally for what functions objc calls. And then we find that there are seven functions that call its initialization, but what do we care about? This means that rwe will create its own categories, add methods, protocols, attributes, etc. This is also consistent with WWDC2020
attachCategories
Classification of relateddemangledName
Look at the source code is not importantclass_setVersion
The class sets the version number API, which is not important. It just assigns the version value to rWEaddMethods_finish
Add methodsclass_addProtocol
Add the agreement_class_addProperty
Add attributesobjc_duplicateClass
This API is called by Apple, users can not call, KVO related. Maybe you’ll find something when you dig underneath the KVO? Not much to say here, except that the KVO system creates its own subclass
Classification loading process
One of the important factors we have learned from the above is that one of the conditions for rWE is due to classification, and as I mentioned in class loading, class loading is also related to classification loading. I don’t know if you’ve noticed attachToClass from this article. But instead of exploring that, let’s go backwards from attachCategories, and explore who called it. I noticed that one of them has attachToClass, and there’s a load_categories_nolock function that I don’t know if you noticed that in the load_images process. So we’ve identified two
load_categories_nolock
In the load_images processloadAllCategories
Function calls.attachToClass
It’s in the map_images processmethodizeClass
Function calls.
Tip: If you’re confused by my analysis above, remember that both the load_images and map_images processes deal with classification. In other words, when we load a class (map_images non-lazily loaded class), there may be a situation where the class is handled. This is also done when you call the load method. (This description is not accurate, I hope you continue to read)
AttachToClass analysis
This function is required in the MAP_images process. But when I ran the test a few times later, I found that the attachCategories function was never entered from this function at all, but is that true? In fact, I tested it again and found that it would enter under certain conditions. In general, this function is not important. It is attached to the class as the function name. It is just an intermediate method
void attachToClass(Class cls, Class previously, int flags) { runtimeLock.assertLocked(); ASSERT((flags & ATTACH_CLASS) || (flags & ATTACH_METACLASS) || (flags & ATTACH_CLASS_AND_METACLASS)); auto &map = get(); auto it = map.find(previously); Bool isMeta = CLS ->isMetaClass(); const char * clsName = cls -> nonlazyMangledName(); const char *person = "person"; if (strcmp(clsName, person) == 0) { if (! isMeta) { printf("-----%s-----%s\n", __func__, clsName); } // attachCategories if (it! = map.end()) { category_list &list = it->second; if (flags & ATTACH_CLASS_AND_METACLASS) { int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS; attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS); attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS); } else { attachCategories(cls, list.array(), list.count(), flags); } map.erase(it); }}Copy the code
Load_categories_nolock analysis
Regardless of what this method does, there are two ways to call this method. We only need to look at loadAllCategories (this function simply calls the load_categories_nolock method and will not display the source code).
-
LoadAllCategories (load_images) process. But to perform this function is a important condition didInitialAttachCategories startup of the category of the initial attachment whether has been completed. The MAP_images process is not executed for startup time.
-
_read_images (map_images) process. For load_images, the execution condition is! DidInitialAttachCategories && didCallDyldNotifyRegister, dyld didCallDyldNotifyRegister is whether the call of a function. It is executed at startup time
Static void load_categories_nolock(header_info *hi) {// Hi is the mach-o header, Header / / is there a class attribute bool hasClassProperties = hi - > info () - > hasCategoryClassProperties (); Hi ->catlist(&count) size_t count; Auto processCatlist = [&](category_t * const * catList) {Person (category_t * const * catList) {Person (category_t * const * catList); So the quantity is 3. For (unsigned I = 0; unsigned I = 0; i < count; i++) { category_t *cat = catlist[i]; Person class CLS = remapClass(cat-> CLS); Locstamped_category_t lc{cat, hi}; // Locstamped_category_t lc{cat, hi}; if (! cls) { // Category's target class is missing (probably weak-linked). // Ignore the category. if (PrintConnecting) { _objc_inform("CLASS: IGNORING category \? \? \? (%s) %p with " "missing weak-linked target class", cat->name, cat); } continue; } // This condition does not trigger. // Process this category. if (cls->isStubClass()) { // Stub classes are never realized. Stub classes // don't know their metaclass until they're // initialized, so we have to add categories with // class methods or properties to the stub itself. // methodizeClass() will find them and add them to // the metaclass as appropriate. if (cat->instanceMethods || cat->protocols || cat->instanceProperties || cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { objc::unattachedCategories.addForClass(lc, cls); } } else { // First, register the category with its target class. // Then, Rebuild the class's method Lists (etc) if // the class is realized. // Note: First, register the class to its target class. Then, if the class is implemented, rebuild the method list of the class (and so on). / / if there are the following classification to achieve the if (cat - > instanceMethods | | cat - > separate protocols | | cat - > instanceProperties) {/ / if put those things in class (cls->isRealized()) { attachCategories(cls, &lc, 1, ATTACH_EXISTING); } else { objc::unattachedCategories.addForClass(lc, cls); }} // You can see that you can implement class attributes. But we don't have the if (cat - > classMethods | | cat - > separate protocols | | (hasClassProperties && cat - > _classProperties)) {/ / class if put those things in yuan (cls->ISA()->isRealized()) { attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS); } else { objc::unattachedCategories.addForClass(lc, cls->ISA()); }}}}}; // Get the class from Mach-o, but why two? // _getObjc2CategoryList processCatlist(hi-> catList (&count)); // _getObjc2CategoryList2 processCatlist(hi->catlist2(&count)); }Copy the code
AttachCategories analysis
This is the function that handles the classification load, and the processing of the classification is in here, and we already know that from the above. As you can see, it’s true that we’re dealing with the class here, but changing the structure of the class refers to RWE. It’s not ro. I hope you know that.
static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags) { if (slowpath(PrintReplacedMethods)) { printReplacements(cls, cats_list, cats_count); } if (slowpath(PrintConnecting)) { _objc_inform("CLASS: attaching %d categories to%s class '%s'%s", cats_count, (flags & ATTACH_EXISTING) ? " existing" : "", cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : ""); } /* * Only a few classes have more than 64 categories during launch. * This uses a little stack, and avoids malloc. * * Categories must be added in the proper order, which is back * to front. To do that with the chunking, we iterate cats_list * from front to back, build up the local buffers backwards, * and call attachLists on the chunks. attachLists prepends the * lists, So the final result is in the expected order. */ // constexpr uint32_t ATTACH_BUFSIZ = 64; method_list_t *mlists[ATTACH_BUFSIZ]; property_list_t *proplists[ATTACH_BUFSIZ]; protocol_list_t *protolists[ATTACH_BUFSIZ]; uint32_t mcount = 0; uint32_t propcount = 0; uint32_t protocount = 0; bool fromBundle = NO; bool isMeta = (flags & ATTACH_METACLASS); Auto rwe = CLS ->data()->extAllocIfNeeded(); auto rwe = CLS ->data()->extAllocIfNeeded(); // Cats_count from load_categories_nolock was 1 for (uint32_t I = 0; i < cats_count; i++) { auto& entry = cats_list[i]; Method_list_t *mlist = entry.cat->methodsForMeta(isMeta); If (mlist) {if (McOunt == ATTACH_BUFSIZ) { So let's see what happens when ATTACH_BUFSIZ prepareMethodLists(CLS, mlists, McOunt, NO, fromBundle, __func__); Rwe ->methods. AttachLists (mlists, McOunt); mcount = 0; } mlists[ATTACH_BUFSIZ - ++mcount] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { if (propcount == ATTACH_BUFSIZ) { rwe->properties.attachLists(proplists, propcount); propcount = 0; } proplists[ATTACH_BUFSIZ - ++propcount] = proplist; } // protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta); if (protolist) { if (protocount == ATTACH_BUFSIZ) { rwe->protocols.attachLists(protolists, protocount); protocount = 0; } protolists[ATTACH_BUFSIZ - ++protocount] = protolist; }} if (McOunt > 0) { PrepareMethodLists (CLS, mlists + ATTACH_BUFSIZ - McOunt, McOunt, NO, fromBundle, __func__); rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount); if (flags & ATTACH_EXISTING) { flushCaches(cls, __func__, [](Class c){ // constant caches have been dealt with in prepareMethodLists // if the class still is constant here, it's fine to keep return ! c->cache.isConstantOptimizedCache(); }); }} rwe-> properties.attachlists (proplists + attach_bufsiz-propcount, propcount); Rwe ->protocols. AttachLists (protolists + ATTACH_BUFSIZ - protocount, protocount); }Copy the code
Conclusion:
I know that the above source code analysis is not enough to give us a clear idea of the process of categorization loading, but the source code will give us an idea of what the system is doing when the categorization is loading. The specific process of loading classes and classifications needs to be seen in the following sections.
Class and classification to achieve load method, several cases analysis
If you implement the load method in the load_images process, the class will become a non-lazy-loaded class. If you implement the load method in the load_images process, the class will break the list of methods assigned by mach-o and need to be reloaded through the load_images process
To explore the classification, and the class specific loading process, the two actually affect each other. Here are some examples:
Classes implement load, and classes also implement load.
Will break the original mechanism, the classification itself to load
1. Implement the load method when there is only one classification. (In fact, at least one category implements the load method.)
You can see that the map_images process is used to load the class, and then the load_images process is used to load the class. The load_categories_nolock function directly reads all classes for the class
2. The classification has two or more load methods.
You can see that the map_images process is used to load the class, and then the load_images process is used to load the class. The load_categories_nolock function directly reads all classes for the class. In line with 1
Classes implement load. Classes do not implement load
There is no sorting extra to load the process. And after testing, I tested the RW value for the first time and found that the classification method was included. I know that I thought there might be categories that would accompany the opening of RWE, and then I went to RWE and found nil. Then I looked at the value of ro and found that baseMethods in RO contained classification methods. Therefore, it can be inferred that the system has completed the classification processing during the compilation stage. Did they say there was a problem with the classification runtime loading?
Class does not implement, class implements load
This case is more complicated. The class may or may not be treated as a non-lazily loaded class
1. Only one category is implemented, the other categories are not implemented
There is no sorting extra to load the process. After testing, it is found that baseMethods in RO contains classification methods. The class implements load, the classification does not implement the same
2. Two or more classification implementations
This is a complex case where the class implementation does not go through the map_images process, but the load_images process is passively loaded. That is, the system did not add the class to the non-lazily loaded class table. The class is loaded because of the load method of the class. For this case ro does not contain a method for classification, so it can be said that the classification is loaded at run time
Tip 1: The class is not implemented so you need to register the category in the target class. See the load_categories_NOLock source code
Tip 2: Prepare the load method, but find that the class does not implement it, see prepare_load_methods for the source code. And remember that the attachToClass method I mentioned above performs the additional classification method in one case and that’s the case.
Neither class nor classification is implemented
We all know that the class is implemented when the first message is sent, so I went to look and found that ro already contains the classification method at this point. This means that the class registers the methods in the main class at compile time
conclusion
We can summarize the loading of categories as follows
For the classification of runtime loading: 1. Both class and classification implement the load method; 2. 2. The classification has more than two implementations of the load method
1. The class implements the load method, but the class does not. 2. Class has no implementation, classification has only one implementation; 3. Neither class nor classification is implemented
So hopefully you won’t have to implement the load method all the time, except for some necessary operations. We can go ahead and execute the initialize method.
supplement
There’s going to be some confusion about ro of the class. Where does this value come from? Clean Memory. Why are there several cases where categories are loaded into classes before the application starts. In fact, we can imagine that LLVM can answer our questions. In the LLVM layer (i.e., compile time) you will find that there is a class_ro_t structure identical to objc, but with different members. And then I’m going to look at one of the methods of struct Read, which is the centralized processing of ro. This is why I said in the class loading section that the class is actually loaded at compile time and the class implementation is implemented at run time.