In iOS class loading principles we talked about the readClass method, which matches the name of a class with its address. But we don’t know how to implement the class yet, so let’s explore it.
RealizeClass analysis
We found the code related to class processing in the _read_images function, put the test code in both places and hit a breakpoint. After running the code, we found that only 3781 lines of print were executed (assuming that the load method of the instance was called). The realizeClassWithoutSwift function is then executed. So let’s examine what the realizeClassWithoutSwift function does.
realizeClassWithoutSwift
Auto ro = (const class_ro_t *) CLS ->data() to print baseMethodList Normally this would print out the list of LGPerson’s methods, but it doesn’t. The method has not been loaded yet. So let’s move on to see when the method loading is implemented.
static Class realizeClassWithoutSwift(Class cls, Class previously) { auto ro = (const class_ro_t *)cls->data(); auto isMeta = ro->flags & RO_META; If (ro->flags & RO_FUTURE) {This was a future class.rw data is already allocated. Rw = CLS ->data(); ro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else {// if you're not a metaclass, you get here, Rw = objc::zalloc<class_rw_t>(); // Normal class.allocate writeable class data.rw = objc::zalloc<class_rw_t>(); rw->set_ro(ro); rw->flags = RW_REALIZED|RW_REALIZING|isMeta; cls->setData(rw); } supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); # if SUPPORT_NONPOINTER_ISA if (isMeta) {/ / if the metaclass is set to a non pointer ISA CLS - > setInstancesRequireRawIsa (); } else { bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; InstancesRequireRawIsa = true if (DisableNonpointerIsa) {instancesRequireRawIsa = true; } // SUPPORT_NONPOINTER_ISA #endif /** supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); CLS ->setSuperclass(supercls); cls->initClassIsa(metacls); // Connect this class to its superclass's subclass lists if (supercls) { addSubclass(supercls, cls); } else { addRootClass(cls); } methodizeClass(cls, previously); return cls; }Copy the code
Const ro = (const class_ro_t *) CLS ->data() const ro = (const class_ro_t *) CLS ->data() Let’s look at the methodizeClass break point.
Here you can see that baseMethodList still has no data, which means that the method is still not loaded in here. So let’s go ahead and analyze what’s being done in methodizeClass.
MethodizeClass analysis
So if we go ahead and execute this, when we get to prepareMethodLists you can see that the list has a value here, it just doesn’t print out.
prepareMethodLists
Next we go to the prepareMethodLists method, where we execute the fixupMethodList method and print the list with the same address as method_list_t *list = ro->baseMethods(); The address here is the same. Next comes the repair method.
fixupMethodList
static void fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort) { runtimeLock.assertLocked(); ASSERT(! mlist->isFixedUp()); if (! mlist->isUniqued()) { mutex_locker_t lock(selLock); // Unique selectors in list. for (auto& meth : *mlist) {// sel_cname = SEL const char *name = sel_cname(meth. Name ()); Printf (" before: %s - %p",name,meth. Name ()); Meth.setname (sel_registerNameNoLock(name, bundleCopy)); meth.setName(sel_registerNameNoLock, bundleCopy)); } // If (int (int (int (int (int (int (int (int (int (int)), int (int (int)))); mlist->isSmallList() && mlist->entsize() == method_t::bigSize) { method_t::SortBySELAddress sorter; std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter); } for (auto& meth: *mlist) {// sel_cname method gets SEL const char *name = sel_cname(meth. Name ()); Printf (" after: %s - %p",name,meth. Name ()); } // Mark method list as uniqued and sorted. // Can't mark small lists, since they're immutable. if (! mlist->isSmallList()) { mlist->setFixedUp(); }}Copy the code
In fixupMethodList approach will traverse the mlist, set the name and address of sel to meth, then according to the address to reorder the mlist.
Finally we can see that the list of methods has indeed been reordered by printing validation.
After executing the fixupMethodList method we print the baseMethodList again and there is no data, so let’s move on.
Lazy-loaded classes versus non-lazy-loaded classes
Non-lazily loaded classes
Non-lazily loaded classes: the current class implements the load method, which is loaded when map_images is implemented.
As we mentioned above, the realizeClassWithoutSwift method is implemented only if the current class implements the load method. In this case, the class is non-lazily loaded. Read_iamges -> read_class -> realizeClassWithoutSwift(ro-rw superclass isa) -> methodizeClass() -> PrepareMethodLists (write method names + sort method lists) The disadvantage of this is that it is time consuming and memory consuming, so what processes will be executed if the load is lazy? Let’s comment the load method here and take a look.
Lazy loading class
Lazy loading: The current class does not implement the load method. The class is loaded only when the first message is received. This saves memory and improves the loading speed.
So when we annotate the load method to run we’re going to go here and execute the realizeClassWithoutSwift method. We add the test code here and print the breakpoint BT to output the function call stack information.
Here you can see a lazy loading class loading process lookUpImpOrForward – > realizeClassMaybeSwiftMaybeRelock – > realizeClassWithoutSwift – > methodizeClass.
And you can see in the main function that the lazy-loaded class is being loaded when LGPerson first calls the method.
Nature of classification
#import <Foundation/Foundation.h> #import "LGPerson.h" #import <objc/runtime.h> extern void _objc_autoreleasePoolPrint(void); @interface LGPerson (LG) @property (nonatomic, copy, nullable) NSString *cate_name; @property (nonatomic, assign) int cate_age; - (void)cate_instanceMethod1; - (void)cate_instanceMethod2; + (void)cate_classMethod3; @end @implementation LGPerson (LG) - (void)cate_instanceMethod1 { NSLog(@"%s",__func__); } - (void)cate_instanceMethod2 { NSLog(@"%s",__func__); } + (void)cate_classMethod3 { NSLog(@"%s",__func__); } @end int main(int argc, const char * argv[]) { @autoreleasepool { LGPerson *p = [LGPerson alloc]; [p say1]; NSLog(@"Hello, World!" ); } return 0; } __attribute__ ((constructor)) void kcFunc (void) {printf (" : % s \ n ", __func__); }Copy the code
Let’s add a class LGPerson (LG) in main.m, CD it to the directory where main.m is, and run the clang-rewrite-objc main.m -o main. CPP command. You will see that a main.cpp file has been generated.
We open the main.cpp file and you can see the underlying code for LGPerson (LG) at the end. Next we click on _category_t to see the structure of the category. We can see that we have a name in the structure, which is LG, CLS which is LGPerson class, and we can see that instance_methods and class_methods, There are more class_methods structures than classes because classes do not have metaclasses, followed by protocols and properties.
Here we can also see the corresponding class methods and object methods that we added, but there are no set methods and no get methods. This also verifies that class adding properties is handled by the associated object. Here is the generated CPP file that we see, so is the structure of the classification in the source code the same as here?
You can see here that the structure is basically the same as what we saw in the CPP file, except for the addition of _classProperties, but the class properties are not always there.