In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly introduces "iOS principle analysis of how to look at load and initialize methods from the source code", in daily operation, I believe that many people in the iOS principle analysis of how to look at load and initialize methods from the source code have doubts, Xiaobian consulted all kinds of data, sorted out a simple and useful method of operation, hope to answer the "iOS principle analysis of how to look at load and initialize methods from the source code" of doubt. Next, please follow the editor to study!
I. introduction
In iOS development, the NSObject class is the base class of everything, and it is very important in the class architecture of Objective-C, in which there are two famous methods: the load method and the initialize method.
+ (void) load;+ (void) initialize
Speaking of these two methods, your first reaction must be that they are too old-fashioned. The timing and function of these two methods have almost become the required questions in the iOS interview. The timing of the call itself is also very simple:
1. The load method is called during the pre-main phase, and each class is called only once.
2. The initialize method is called before the class or subclass makes the first method call.
The above two points are correct in themselves, but in addition, there are many questions worthy of our deep study, such as:
1. What is the order in which the load methods of the subclass and the parent class are called?
two。 What is the order in which load methods are called for classes and categories?
3. If the subclass does not implement the load method, will the parent class be called?
4. What happens when multiple classifications implement the load method?
5. What is the order in which the load methods of each class are called?
6. What is the order of method calls for the initialize of the parent class and the child class?
7. After the subclass implements the initialize method, will the initialize method of the parent class be called?
8. What happens when multiple classifications implement the initialize method?
9....
Can you give a clear answer to all the questions mentioned above? In fact, the load and initialize methods themselves have many very interesting features, this blog, we will combine Objective-C source code, the principle of the implementation of these two methods to do in-depth analysis, I believe that if you do not know enough about load and initialize, can not fully understand the above questions, then this blog will make it fruitful. Whether in a future interview or when using load and initialize methods at work, it may help you understand how it works from the source code.
Second, practice leads to true knowledge-first look at the load method
Before starting the analysis, we can first create a test project and do a simple test of the timing of the execution of the load method. First, we create a command line program project for Xcode, in which some classes, subclasses, and classifications are created for us to test. The directory structure is shown in the following figure:
Among them, MyObjectOne and MyObjectTwo are both classes inherited from NSObject, MySubObjectOne is a subclass of MyObjectOne, and MySubObjectTwo is a subclass of MyObjectTwo. At the same time, we also create three classifications, implement the load method in the class, and do print processing, as follows:
+ (void) load {NSLog (@ "load:%@", [self className]);}
Similarly, a similar implementation is made in the classification:
+ (void) load {NSLog (@ "load-category:%@", [self className]);}
Finally, we add a Log to the main function:
Int main (int argc, const char * argv []) {@ autoreleasepool {NSLog (@ "Main");} return 0;}
Run the project, and the printed result is as follows:
2021-02-18 14 KCObjc 33 KCObjc [21400 KCObjc 23090040] load:MySubObjectOne2021-02-18 1433 KCObjc 46.773959 0800 KCObjc [21400 KCObjc 23090040] load:MyObjectTwo2021-02-18 140033 33 KCObjc 46.774008 0800 load:MySubObjectTwo2021-02-18 1400 33 KCObjc 46.7740523090040] load:MySubObjectTwo2021-02-18 141400 33 KCObjc 46.774052300040] Load-category:MyObjectOne2021-02-18 14 load-category:MyObjectOne2021 33 KCObjc 46.774127 Main 0800 KCObjc [21400 load-category:MyObjectOne2021 23090040]
From the print result, we can see that the load method is called before the main method starts. In terms of execution order, the load method of the class is called first, and then the classified load method is called. From the relationship between the parent and child classes, the load method of the parent class is called first, and then the load method of the child class is called.
Next, we will analyze from the source code, the system so called the load method, is derived from what kind of mystery.
Third, analyze the call of the load method from the source code
To further study the load method, we need to start with the initialization function of Objective-C:
Void _ objc_init (void) {static bool initialized = false; if (initialized) return; initialized = true; / / fixme defer initialization until an objc-using image is found? Environ_init (); tls_init (); static_init (); runtime_init (); exception_init (); cache_init (); _ imp_implementationWithBlock_init (); / / We don't need to pay attention to anything else, just pay attention to this line of code _ dyld_objc_notify_register (& map_images, load_images, unmap_image); # if _ OBJC2__ didCallDyldNotifyRegister = true;#endif}
The _ objc_init function is defined in the objc-os.mm file. This function is used to initialize the Objective-C program and is called by the bootstrap program. The call is actually very early, and it is a complex call driver for the operating system bootstrap and has no effect on the developer. In the _ objc_init function, operations such as environment initialization, runtime initialization and cache initialization are performed. A very important step is to execute the _ dyld_objc_notify_register function, which calls the load_images function to load the image.
The call to the load method is actually a step in the class loading process. First, let's look at the implementation of a load_images function:
Voidload_images (const char * path _ unused, const struct mach_header * mh) {if (! didInitialAttachCategories & & didCallDyldNotifyRegister) {didInitialAttachCategories = true; loadAllCategories ();} / / Return without taking locks if there are no + load methods here. If (! hasLoadMethods ((const headerType *) mh) return; recursive_mutex_locker_t lock (loadMethodLock); / / Discover load methods {mutex_locker_t lock2 (runtimeLock); prepare_load_methods ((const headerType *) mh);} / / Call + load methods (without runtimeLock-re-entrant) call_load_methods ();}
Filter out the parts that we don't care about, and the core related to load method calls is as follows:
Voidload_images (const char * path _ _ unused, const struct mach_header * mh) {/ / there is no load method in the image. Return if (! hasLoadMethods ((const headerType *) mh)) return; {/ / prepare the load method prepare_load_methods ((const headerType *) mh);} / call the load method call_load_methods ();}
The core part is the preparation of the load method and the call of the laod method. Let's take a look at the preparation of the load method step by step (we removed the unimportant part):
Void prepare_load_methods (const headerType * mhdr) {size_t count, I; / / get the composition list of all classes classref_t const * classlist = _ getObjc2NonlazyClassList (mhdr, & count); for (I = 0; I
< count; i++) { // 将所有类的load方法进行整理 schedule_class_load(remapClass(classlist[i])); } // 获取所有的分类 组成列表 category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count); for (i = 0; i < count; i++) { category_t *cat = categorylist[i]; // 将分类的load方法进行整理 add_category_to_loadable_list(cat); }} 看到这里,我们基本就有头绪了,load方法的调用顺序,基本可以确定是由整理过程所决定的,并且我们可以发现,类的load方法整理与分类的load方法整理是互相独立的,因此也可以推断其调用的时机也是独立的。首先我们先来看类的load方法整理函数schedule_class_load(去掉无关代码后): static void schedule_class_load(Class cls){ // 类不存在或者已经加载过load,则return if (!cls) return; if (cls->Data ()-> flags & RW_LOADED) return; / / ensures the loading order and recursively loads the parent class schedule_class_load (cls- > superclass); / / loads the load method of the current class into the load method list add_class_to_loadable_list (cls); / / sets the current class to have already loaded laod cls- > setInfo (RW_LOADED);}
As you can see, the schedule_class_load function uses recursion to perform the inheritance chain up layer by layer, ensuring that when loading the load method, the parent class is loaded first, and then the subclass. Add_class_to_loadable_list is the core load method collation function, as follows (excluding irrelevant code):
Void add_class_to_loadable_list (Class cls) {IMP method; / / read the load method in the class method = cls- > getLoadMethod (); the load method is not implemented in the if (! method) return; / / class, and directly return / / build the storage list and expansion logic if (loadable_classes_used = = loadable_classes_allocated) {loadable_classes_allocated = loadable_classes_allocated*2 + 16 Loadable_classes = (struct loadable_class *) realloc (loadable_classes, loadable_classes_allocated * sizeof (struct loadable_class));} / / add a loadable_class structure to the list, which stores the class and the corresponding laod method loadable_ [loadable _ classes_used]. Cls = cls Loadable_ classes [loadable _ classes_used]. Method = method; / / pointer to tag list index moves loadable_classes_used++;}
The definition of loadable_clas structure is as follows:
Struct loadable_class {Class cls; / / may be nil IMP method;}
The implementation of the getLoadMetho function is to obtain the implementation of the load method from the class, as follows:
IMP objc_class::getLoadMethod () {/ / get the method list const method_list_t * mlist; mlist = ISA ()-> data ()-> ro ()-> baseMethods (); if (mlist) {/ / traverse, find the load method and return for (const auto& meth: * mlist) {const char * name = sel_cname (meth.name) If (0 = = strcmp (name, "load") {return meth.imp;} return nil;}
Now, the preparation logic for the load method of the class is very clear, and the load methods of all classes will eventually be added to the list named loadable_classes in the order of the parent class and then the child class. You should pay attention to the name loadable_classes, which we will encounter later.
Let's take a look at the laod method preparation process for classification, which is very similar to the class we introduced above. The add_category_to_loadable_list function is simplified as follows:
Void add_category_to_loadable_list (Category cat) {IMP method; / / get the load method of the current classification method = _ category_getLoadMethod (cat); if (! method) return; / / list creation and expansion logic if (loadable_categories_used = = loadable_categories_allocated) {loadable_categories_allocated = loadable_categories_allocated*2 + 16 Loadable_categories = (struct loadable_category *) realloc (loadable_categories, loadable_categories_allocated * sizeof (struct loadable_category));} / / storing loadable_ categories with the load method [loadable _ categories_used]. Cat = cat; loadable_ categories [loadable _ categories_used]. Method = method Loadable_categories_used++;}
As you can see, the load method for the final classification is stored in the loadable_categories list.
Once the load method is ready, let's analyze the execution process of the load method. The core implementation of the call_load_methods function is as follows:
Void call_load_methods (void) {bool more_categories; do {/ / traverses the loadable_classes first. The loadable_classes_used field can be understood as the number of elements in the list while (loadable_classes_used > 0) {call_class_loads ();} / / then traversing the category calls more_categories = call_category_loads () } while (loadable_classes_used > 0 | | more_categories);}
The simplified implementation of the call_class_loads function is as follows:
Static void call_class_loads (void) {int I; / / loadable_classes list struct loadable_class * classes = loadable_classes; / / number of load methods to be executed int used = loadable_classes_used; / / Clean data loadable_classes = nil; loadable_classes_allocated = 0; loadable_classes_used = 0; / / the sequence of loops for execution is for from front to back (I = 0; I
< used; i++) { // 获取类 Class cls = classes[i].cls; // 获取对应load方法 load_method_t load_method = (load_method_t)classes[i].method; if (!cls) continue; // 执行load方法 (*load_method)(cls, @selector(load)); }} call_category_loads函数的实现要复杂一些,简化后如下: static bool call_category_loads(void){ int i, shift; bool new_categories_added = NO; // 获取loadable_categories分类load方法列表 struct loadable_category *cats = loadable_categories; int used = loadable_categories_used; int allocated = loadable_categories_allocated; loadable_categories = nil; loadable_categories_allocated = 0; loadable_categories_used = 0; // 从前往后遍历进行load方法的调用 for (i = 0; i < used; i++) { Category cat = cats[i].cat; load_method_t load_method = (load_method_t)cats[i].method; Class cls; if (!cat) continue; cls = _category_getClass(cat); if (cls && cls->IsLoadable () {(* load_method) (cls, @ selector (load)); cat [I] .cat = nil;}} return new_categories_added;}
Now, I believe you have an idea of why the load method is called first, classified and then called, and why the parent class is called first and the child class is called later. However, it is not clear how the order of calls between classes or categories is determined. As you can see from the source code, the list of classes is obtained through the _ getObjc2NonlazyClassList function, and the list of the same categories is obtained through the function _ getObjc2NonlazyCategoryList. The order of classes or classifications obtained by these two functions is actually related to the compilation order of the class source files, as shown in the following figure:
As you can see, the execution order of the printed load method is consistent with the compilation order of the source code.
4. Analysis of initialize method
We can use the same strategy as when analyzing the load method to test the execution of the initialize method, first adding the implementation of the initialize method to all the classes in the test project. If you run the project directly at this point, you will find that there is no output on the console, because the initialize method is executed only when the method of the class is called for the first time, and the following test code is written in the main function:
Int main (int argc, const char * argv []) {@ autoreleasepool {[MySubObjectOne new]; [MyObjectOne new]; [MyObjectTwo new]; NSLog (@ "-"); [MySubObjectOne new]; [MyObjectOne new]; [MyObjectTwo new];} return 0;}
Run the code console to print as follows:
2021-02-18 21 KCObjc 29KCObjc 55.761897251232] initialize-cateOne:MyObjectOne2021-02-18 2121 KCObjc 55.762526 0800 initialize:MySubObjectOne2021-02-18 2129 KCObjc 55.762621232 initialize-cate:MyObjectTwo2021-02-18 2129 KCObjc 55.762665 0800 KCObjc
You can see that the printed data all appear in front of the split line, indicating that once the initialize method of a class is called, messages will not be sent to the class later, and the initialize method will not be called again. It should also be noted that if a message is sent to the subclass, the initialize of the parent class will be called first, and then the initialize of the subclass. At the same time, if the initialize method is implemented in the classification, the class itself will be overwritten. And the lower loading order of the classification will overwrite the previous one. Let's analyze the calling characteristics of the initialize method through the source code.
First, when the class method of the class is called, the class_getClassMethod method in runtime is executed to find the implementation function, which is implemented in the source code as follows:
Method class_getClassMethod (Class cls, SEL sel) {if (! cls | |! sel) return nil; return class_getInstanceMethod (cls- > getMeta (), sel);}
As can be seen from the source code, calling the class method of a class is actually calling the example method of its metaclass, and the getMeta function is used to obtain the metaclass of the class. We will not expand the organization principles of the class and metaclass here. What we need to focus on is the class_getInstanceMethod function, which is also very simple to implement, as follows:
Method class_getInstanceMethod (Class cls, SEL sel) {if (! cls | |! sel) return nil; / / query the method list and try to parse the related work lookUpImpOrForward (nil, sel, cls, LOOKUP_RESOLVER); / / get the method return _ class_getMethod (cls, sel) from the class object;}
In the implementation of the class_getInstanceMethod method, _ class_getMethod is the function that finally gets the method to be called. Before that, the lookUpImpOrForward function will do some pre-operations, including the calling logic of the initialize function. We remove the extraneous logic. The core implementation of lookUpImpOrForward is as follows:
IMP lookUpImpOrForward (id inst, SEL sel, Class cls, int behavior) {IMP imp = nil; / / the core is! cls- > isInitialized () if the current class has not been initialized, the initializeAndLeaveLocked function if (slowpath ((behavior & LOOKUP_INITIALIZE) & &! cls- > isInitialized () {cls = initializeAndLeaveLocked (cls, inst, runtimeLock);} return imp;}
InitializeAndLeaveLocked calls the initializeAndMaybeRelock function directly, as follows:
Static Class initializeAndLeaveLocked (Class cls, id obj, mutex_t& lock) {return initializeAndMaybeRelock (cls, obj, lock, true);}
The initialization logic of the class is done in the initializeAndMaybeRelock function. This process is thread-safe, and the core code is as follows:
Static Class initializeAndMaybeRelock (Class cls, id inst, mutex_t& lock, bool leaveLocked) {/ / if it has been initialized, return if (cls- > isInitialized ()) {return cls;} / / find the non-metaclass of the current class Class nonmeta = getMaybeUnrealizedNonMetaClass (cls, inst); / / initialize initializeNonMetaClass (nonmeta); return cls;}
The initializeNonMetaClass function will query up the inheritance chain recursively to find all uninitialized parent classes for initialization. The core implementation is simplified as follows:
Void initializeNonMetaClass (Class cls) {whether the Class supercls; / / tag needs to be initialized bool reallyInitialize = NO; / / if the parent class exists and has not been initialized, the parent class is initialized recursively supercls = cls- > superclass; if (supercls & &! supercls- > isInitialized ()) {initializeNonMetaClass (supercls);} SmallVector localWillInitializeFuncs {/ / if the current class is not currently initializing and the current class has not initialized if (! cls- > isInitialized () & &! cls- > isInitializing ()) {/ / set the initialization flag as cls- > setInitializing (); / / the tag needs to initialize reallyInitialize = YES }} / / need to initialize if (reallyInitialize) {@ try {/ / call the initialization function callInitialize (cls);} @ catch (...) {@ throw;} return;}}
The callInitialize function will eventually call the objc_msgSend function to send the initialize initialization message to the class, as follows:
Void callInitialize (Class cls) {((void (*) (Class, SEL)) objc_msgSend) (cls, @ selector (initialize)); asm (");}
It should be noted that the biggest difference between the initialize method and the load method is that it is finally implemented through objc_msgSend. If each class is not initialized, it will send an initialize message to the class through objc_msgSend. Therefore, if the subclass does not implement initialize, according to the message mechanism of objc_msgSend, it will find the implementation of the parent class all the way up the inheritance chain and call it, and all initialize methods will not be called only once. If this method is implemented in the parent class, and it has multiple subclasses that do not implement this method, then when each subclass accepts a message for the first time, it will call the parent's initialize method, which is very important and must be kept in mind in actual development.
At this point, on the "iOS principle analysis of how to look at load and initialize methods from the source code" on the end of the study, hoping to solve everyone's doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.