In conjunction with the previous read_images article, this article continues to examine the class loading process. In the last article, we have identified realizeClassWithoutSwift, the core process for non-lazily loaded class initialization. Now we take a closer look at realizeClassWithoutSwift.
1. Class initialization exploration
In the read_images process, some fixes will be made to the class, such as fixing the selector mess during compilation, fixing some messages, confusing class handling, etc. At the same time, the class name is associated with the class, inserted into the mapping table, and the class table in memory is updated.
At this time, there are no methods, protocols, attributes, etc., and some extensions of the MachO file are not inserted into the class. So when is the information about the MachO file inserted into the memory corresponding CLS? See below:
This is the key process of class initialization in read_images. We can see from the comments that we want to initialize a class that is not lazily loaded. What is a class that is not lazily loaded? The nlclslist function is used to obtain the list of non-lazily loaded classes, and the class is processed recursively to complete the initialization of non-lazily loaded classes.
2. RealizeClassWithoutSwift analysis
The old way, filtering out the classes that we need to study. Add a filter condition in this section and set a breakpoint to filter out LGPerson. We implement the load method in LGPerson, and the program successfully enters the realizeClassWithoutSwift process. The source code is as follows:
static Class realizeClassWithoutSwift(Class cls, Class previously) { const char *mangledName = cls->nonlazyMangledName(); if (strcmp(mangledName, "LGPerson") == 0) { printf("LGPerson...." ); } runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; if (! cls) return nil; if (cls->isRealized()) { validateAlreadyRealizedClass(cls); return cls; } ASSERT(cls == remapClass(cls)); // fixme verify class is not in an un-dlopened part of the shared cache? // rw () -ro -> rw () -ro -> rw () -ro -> rw () -ro -> rw () -ro -> rw) Auto ro = (const class_ro_t *) CLS ->data(); auto isMeta = ro->flags & RO_META; Allocated tokens. Rw = CLS ->data(); if (ro->flags & RO_FUTURE) {// This was a future class.rw data is already allocated. ro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // 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); } // Clean memory address ro dirty memory address rw // Run time -memory ro-copy -rw memory operation is serious // not every class needs to be inserted, modify very little, (rW expansion will cause a lot of memory, copy ro directly), If the dynamic extension a rwe CLS - > cache. InitializeToEmptyOrPreoptimizedInDisguise (); #if FAST_CACHE_META if (isMeta) cls->cache.setBit(FAST_CACHE_META); #endif // Choose an index for this class. // Sets cls->instancesRequireRawIsa if indexes no more indexes are available cls->chooseClassArrayIndex(); if (PrintConnecting) { _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s", cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex(), cls->isSwiftStable() ? "(swift)" : "", cls->isSwiftLegacy() ? "(pre-stable swift)" : ""); } // Realize superclass and metaclass, if they aren't already. // This needs to be done after RW_REALIZED is set above, for root classes. // This needs to be done after class index is chosen, for root metaclasses. // This assumes that none of those classes have Swift contents, // or that Swift's initializers have already been called. // fixme that assumption will be wrong if we add support // For ObjC subclasses of Swift classes. // Recursion, Supercls = realizeClassWithoutSwift(CLS ->getSuperclass()), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); #if SUPPORT_NONPOINTER_ISA if (isMeta) { // Metaclasses do not need any features from non pointer ISA // This allows for a faspath for classes in objc_retain/objc_release. cls->setInstancesRequireRawIsa(); } else { // Disable non-pointer isa for some classes and/or platforms. // Set instancesRequireRawIsa. bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; if (DisableNonpointerIsa) { // Non-pointer isa disabled by environment or app SDK version instancesRequireRawIsa = true; } else if (! hackedDispatch && 0 == strcmp(ro->getName(), "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->getSuperclass() && supercls->instancesRequireRawIsa()) { // This is also propagated by addSubclass() // but nonpointer isa setup needs it earlier. // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass instancesRequireRawIsa = true; rawIsaIsInherited = true; } if (instancesRequireRawIsa) { cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); }} // SUPPORT_NONPOINTER_ISA #endif // Update superclass and metaclass in case of remapping // cls->setSuperclass(supercls); cls->initClassIsa(metacls); // Reconcile instance variable offsets / layout. // This may reallocate class_ro_t, updating our ro variable. if (supercls && ! isMeta) reconcileInstanceVariables(cls, supercls, ro); // Set fastInstanceSize if it wasn't set already. cls->setInstanceSize(ro->instanceSize); // Copy some flags from ro to rw if (ro->flags & RO_HAS_CXX_STRUCTORS) {CLS ->setHasCxxDtor(); if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) { cls->setHasCxxCtor(); } } // Propagate the associated objects forbidden flag from ro or from // the superclass. if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) || (supercls && supercls->forbidsAssociatedObjects())) { rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS; } // Connect this class to its superclass's subclass lists if (supercls) {addSubclass(supercls, CLS); } else { addRootClass(cls); } // Attach category methodizeClass(CLS, previously); return cls; }Copy the code
1. Source code analysis
-
First, declare related variables to determine whether the class has been implemented. If it has been implemented, return directly. For recursive control, because later in the process we’re going to recursively implement the superclass and metaclass, the root class’s superclass is nil, and the metaclass’s ISA points to itself, so this guarantees that the class will only be initialized once.
if (! cls) return nil; if (cls->isRealized()) { validateAlreadyRealizedClass(cls); return cls; }Copy the code
-
Rw initialization. This refers to the concepts of clean memory and dirty memory.
Ro belongs to Clean Memory
.The memory space that is determined at edit time
, read-only memory space that does not change after loading, including information about class names, methods, protocols, and instance variables;rw
The data space belongs todirty memory
.rw
Is a run-time structure that can be read and written, and because of its dynamic nature, you can add properties, methods, and protocols to a class.Memory that changes at run time
.rwe
Class. inWWDC2020
There is only less than10%
Not every class needs to insert data. Few classes make changes, avoiding resource consumption, so there it isrwe
.
auto ro = (const class_ro_t *)cls->data(); auto isMeta = ro->flags & RO_META; Allocated tokens. Rw = CLS ->data(); if (ro->flags & RO_FUTURE) {// This was a future class.rw data is already allocated. ro = cls->data()->ro(); ASSERT(! isMeta); cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // 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); }Copy the code
In this process, the data address obtained from machO is forced according to the class_RO_T format, the space of RW is initialized, and a copy of ro data is put into RW.
-
Recursive processing, the implementation of the parent class and metaclass.
Supercls = realizeClassWithoutSwift(remapClass(CLS ->superclass), nil); metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);Copy the code
-
Isa processing, in the previous Isa learning, for NONPOINTER_ISA bit field processing, pointer optimization, Isa end bit is 1, Isa is not just a pointer. For metaclasses and some classes in special case scenarios, do not need to turn on the pointer optimization class, use Raw Isa, the end bit of Isa is 0.
#if SUPPORT_NONPOINTER_ISA if (isMeta) { // Metaclasses do not need any features from non pointer ISA // This allows for a faspath for classes in objc_retain/objc_release. cls->setInstancesRequireRawIsa(); } else { // Disable non-pointer isa for some classes and/or platforms. // Set instancesRequireRawIsa. bool instancesRequireRawIsa = cls->instancesRequireRawIsa(); bool rawIsaIsInherited = false; static bool hackedDispatch = false; if (DisableNonpointerIsa) { // Non-pointer isa disabled by environment or app SDK version instancesRequireRawIsa = true; } else if (! hackedDispatch && 0 == strcmp(ro->getName(), "OS_object")) { // hack for libdispatch et al - isa also acts as vtable pointer hackedDispatch = true; instancesRequireRawIsa = true; } else if (supercls && supercls->getSuperclass() && supercls->instancesRequireRawIsa()) { // This is also propagated by addSubclass() // but nonpointer isa setup needs it earlier. // Special case: instancesRequireRawIsa does not propagate // from root class to root metaclass instancesRequireRawIsa = true; rawIsaIsInherited = true; } if (instancesRequireRawIsa) { cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited); } } // SUPPORT_NONPOINTER_ISA #endifCopy the code
-
Establish a double fixed list relationship between the subclass and the parent class to ensure that the subclass can find the parent class and the parent class can also find the subclass. Initializes the size of the instance object of the class, whether there are c++ destructor Settings, and the associated object Settings.
// Update superclass and metaclass in case of remapping // CLS ->setSuperclass(supercls); cls->initClassIsa(metacls); // Reconcile instance variable offsets / layout. // This may reallocate class_ro_t, updating our ro variable. if (supercls && ! isMeta) reconcileInstanceVariables(cls, supercls, ro); // Set fastInstanceSize if it wasn't Set already. // Set the size of the class instance - CLS -> Set instanceSize (ro->instanceSize); // Copy some flags from ro to rw if (ro->flags & RO_HAS_CXX_STRUCTORS) {CLS ->setHasCxxDtor(); if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) { cls->setHasCxxCtor(); } } // Propagate the associated objects forbidden flag from ro or from // the superclass. if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) || (supercls && supercls->forbidsAssociatedObjects())) { rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS; } // Connect this class to its superclass's subclass lists if (supercls) {addSubclass(supercls, CLS); } else { addRootClass(cls); }Copy the code
-
cls->setInstanceSize(ro->instanceSize);
When creating an object, we use instanceSize to initialize the size of the object in the class_createInstanceFromZone process.
-
-
Methodize the current class, add methods, protocols, attributes to the class, and sort the list of methods.
// Attach categories methodizeClass(CLS, previously); // Attach categories methodizeClass(CLS, previously);Copy the code
2. Track CLS data changes
In the same way, we add a copy of key code to the process to filter out the class LGPerson that we want to explore.
const char * className = "LGPerson"; if (strcmp(class_getName(cls), className) == 0) { printf("hello LGPerson..." ); }Copy the code
Set breakpoints to determine whether LLDB prints CLS data structure changes before RW initialization. Run program to print CLS contents, unable to read RO data. See below:
auto ro = (const class_ro_t *)cls->data();
To obtainMachO file
According to the data address inclass_ro_t
The format is strongly transferred.auto isMeta = ro->flags & RO_META;
Determine whether it is a metaclass.rw = objc::zalloc<class_rw_t>();
Initialize therw
That will bero
Copy torw
And willcls
thebits.data
Set torw
.
LLDB validates the result as follows:
The LLDB prints the CLS data structure and successfully obtains ro data from the CLS.
- supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
- metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
Continuing to run the program will recursively complete the superclasses and metaclasses and implementations. In this case, class LGPerson is inherited from LGHuman, where LGPerson is non-lazily loaded, that is, it implements the +load method. See below:
After completing the recursive process, we can see how the LGPerson class changes in the data:
3. MethodizeClass analysis
After realizeClassWithoutSwift completes the initialization of the class, the RW has been created and is able to obtain the related properties, methods, and so on from the CLS. So the last line of code in realizeClassWithoutSwift what does the methodizeClass method do?
1. Source code analysis
First, read the source code:
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name()));
}
ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
}
#endif
}
Copy the code
-
Processes methods, attributes, and protocols. See below:
But there is an interesting problem here. Rwe is empty. Obviously, the class has not been extended yet, so rWE has not been created and initialized. At this time for the method, attribute, protocol add operation invalid!
-
The handling of method lists is a little different, with the prepareMethodLists method being called. So what does this method do? See below:
The core process, fixupMethodList, fixes selectors as needed, based on the comment: Enter the fixupMethodList method to see the implementation process. See below:
-
Continue the interpretation of methodizeClass source code. The key steps in the class initialization process are found, such as adding classification methods and protocols to the class, including the initialization of RWE.
2. PrepareMethodLists validation
PrepareMethodLists is a method list ordering process, so let’s check it out! Before sorting, LLDB outputs a list of methods:
After going through the prepareMethodLists process, LLDB outputs a list of methods:
The order of methods has changed. Output sel addresses and find that they are linearly incrementing, which verifies that in slow searches, the method list binary search algorithm is possible. Binary search requires that the linear table must adopt the sequential storage structure and the elements in the table are arranged in order by keywords. See below:
4. AttachCategories exploration
In the methodizeClass process, there is still a key line of code that has not been parsed! Add class-related content to the class. See the following code:
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
Copy the code
Continue running the code into the attachToClass method as shown below:
Unfortunately, it did not get into the IF, i.e. attachCategories method!
- If the forward search doesn’t work, go backwards, so
attachCategories
Where do you call it?
1. AttachCategories backward derivation
In the libObjc. Dylib library, the globally searched attachCategories method. See below:
The attachCategories method was called in the load_categories_NOLock method. Where does load_categories_NOLock apply? Search for load_categories_nolock globally. See the following picture:
After a global search for the loadAllCategories method, it is found that the loadAllCategories method is called in the load_images method. See below:
-
To sort out the process:
- load_images
- loadAllCategories
- load_categories_nolock
- attachCategories
2. Preliminary study on classification treatment
Create a category for LGPerson and add a method sayHello_cate to the category. See the following code:
@implementation LGPerson (LG)
- (void)sayHello_cate{
NSLog(@"sayHello_cate %s", __func__);
}
@end
Copy the code
AttachCategories adds the category content to the class, and adds a filter criterion to the load_categories_NOLock method to filter out the category processing flow of the LGPerson class that we care about! See below:
Run the code and, unfortunately, do not enter the breakpoint. Why is that? This method is called by load_images, so is it related to the load method?
Modify the class LGPerson (LG) and add the load method to it as follows:
@implementation LGPerson (LG)
+ (void)load{}
- (void)sayHello_cate{
NSLog(@"sayHello_cate %s", __func__);
}
@end
Copy the code
Reruning the program, the magic happens, filtering successfully to LGPerson and going to the attachCategories process and getting the method in the LGPerson(LG) category. See below:
The load method is implemented in both the class and the class. The load method is implemented in the class and the class. The load method is implemented in the class and the class.
thenLazily loaded classes and classifications
What is the loading process? That’s what we’re going to explore! Decrypt it in next article!!
5. Analysis of the initialization process of lazy loading classes
- The initialization process for a non-lazily loaded class:
- map_images
- _read_images
- realizeClassWithoutSwift
So what is the initialization process for a lazily loaded class? Set the filter condition in methodizeClass to filter out the LGPerosn class we care about, remove the load method in LGPerosn class and run the code:
We found that the class is initialized when it first sends a message.
- The initialization process of a lazy-loaded class is:
- lookUpImpOrForward
- realizeClassMaybeSwiftMaybeRelock
- realizeClassWithoutSwift
6. Add RW RO RWE for in-depth analysis
Rw and RO have a better understanding!
ro
Belong toclean memory
, memory space determined during editing, read-only, memory space that does not change after loading, including class name, method, protocol and instance variable information;rw
The data space belongs todirty memory
Rw is a run-time structure that can be read and written. Due to its dynamic nature, properties, methods, and protocols can be added to a class. Memory that changes at run time.
What is RWE? What does it have to do with RW and ro? How exactly is it stored inside? See the source code:
// rw
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
private:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
const ro_or_rw_ext_t get_ro_or_rwe() const {
return ro_or_rw_ext_t{ro_or_rw_ext};
}
void set_ro_or_rwe(const class_ro_t *ro) {
ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
}
void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
// the release barrier is so that the class_rw_ext_t::ro initialization
// is visible to lockless readers
rwe->ro = ro;
ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
}
class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
}
// ro
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}
// rwe
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
Copy the code
The key to the internal implementation is:
using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
Copy the code
A template is defined in the bottom layer, using the template mechanism can significantly reduce redundant information, can greatly save program code, and further improve the reusability and maintainability of object-oriented programs. Templates provide methods for determining IS data, obtaining data by GET, and storing data in storeAt.
template <class PT1, class PT2> class PointerUnion { uintptr_t _value; static_assert(alignof(PT1) >= 2, "alignment requirement"); static_assert(alignof(PT2) >= 2, "alignment requirement"); struct IsPT1 { static const uintptr_t Num = 0; }; struct IsPT2 { static const uintptr_t Num = 1; }; template <typename T> struct UNION_DOESNT_CONTAIN_TYPE {}; uintptr_t getPointer() const { return _value & ~1; } uintptr_t getTag() const { return _value & 1; } public: explicit PointerUnion(const std::atomic<uintptr_t> &raw) : _value(raw.load(std::memory_order_relaxed)) { } PointerUnion(PT1 t) : _value((uintptr_t)t) { } PointerUnion(PT2 t) : _value((uintptr_t)t | 1) { } void storeAt(std::atomic<uintptr_t> &raw, std::memory_order order) const { raw.store(_value, order); } template <typename T> bool is() const { using Ty = typename PointerUnionTypeSelector<PT1, T, IsPT1, PointerUnionTypeSelector<PT2, T, IsPT2, UNION_DOESNT_CONTAIN_TYPE<T>>>::Return; return getTag() == Ty::Num; } template <typename T> T get() const { ASSERT(is<T>() && "Invalid accessor called"); return reinterpret_cast<T>(getPointer()); } template <typename T> T dyn_cast() const { if (is<T>()) return get<T>(); return T(); }};Copy the code
In class_rw_t, each of the provided methods has a section of code like this:
auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { v.get<class_rw_ext_t *>()->... // Omit} else {... } // get_ro_or_rwe() const {return ro_or_rw_ext_t{ro_or_rw_ext}; }Copy the code
This can be interpreted as calling get_ro_or_rwe() to get the template ro_or_rw_ext_t; Then call the is
() method of the template to determine whether there is rWE, that is, whether there is class_rw_ext_t data space; If so, the get
() method is called to retrieve the corresponding data from rWE’s data space. So when was RWE created? When methods, protocols, and classification information are dynamically added to this class, the extAllocIfNeeded method is called to initialize the RWE.
class_rw_ext_t *extAllocIfNeeded() { auto v = get_ro_or_rwe(); if (fastpath(v.is<class_rw_ext_t *>())) { return v.get<class_rw_ext_t *>(); } else { return extAlloc(v.get<const class_ro_t *>()); } } class_rw_ext_t * class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy) { runtimeLock.assertLocked(); auto rwe = objc::zalloc<class_rw_ext_t>(); rwe->version = (ro->flags & RO_META) ? 7:0; Class_rw_ext_t ->methods (baseMethodList in ro is placed in class_rw_ext_t->methods); method_list_t *list = ro->baseMethods(); if (list) { if (deepCopy) list = list->duplicate(); rwe->methods.attachLists(&list, 1); } // See comments in objc_duplicateClass // property lists and protocol lists historically // have not been deep-copied // // This is probably wrong and ought to be fixed some day property_list_t *proplist = ro->baseProperties; if (proplist) { rwe->properties.attachLists(&proplist, 1); } protocol_list_t *protolist = ro->baseProtocols; if (protolist) { rwe->protocols.attachLists(&protolist, 1); } set_ro_or_rwe(rwe, ro); return rwe; }Copy the code
As you can see from the source code, when creating the RWE, that is, creating class_rw_ext_t, ro is added to the class_rw_ext_t data structure. So where does ro come from? We have studied it above!! Look at the source code implementation for setting ro:
void set_ro(const class_ro_t *ro) { auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { v.get<class_rw_ext_t *>()->ro = ro; } else { set_ro_or_rwe(ro); } } void set_ro_or_rwe(const class_ro_t *ro) { ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed); } const class_ro_t *ro() const { auto v = get_ro_or_rwe(); / / get the template if (slowpath (v.i s < class_rw_ext_t * > ())) {return v.g et > < class_rw_ext_t * () - > ro; } return v.get<const class_ro_t *>(); }Copy the code
Again, get_ro_or_rwe() is called to get the template ro_or_rw_ext_t; Then call the is
() method of the template to determine whether there is rWE, that is, whether there is class_rw_ext_t data space; If rWE does not exist, it is stored in RW, that is, copied to RW. When obtaining THE RO data, if the RWE already exists, the RO in the RWE is directly returned; if the RWE does not exist, the copy RO in the RW is directly returned.
Conclusion:
During class implementation, rW (class_rw_t) is initialized, ro(class_RO_t) of the class is read from the macho executable, and ro is copied to RW. Rwe is not initialized at this point. At run time, methods and protocols need to be dynamically added to classes, rWE space is created, and data in RO is attached to RWE data structures first. When reading data, the data in RWE is returned first, or if RWE is not initialized, the data in RO is returned. A template is defined in the bottom layer, using the template mechanism can significantly reduce redundant information, can greatly save program code, and further improve the reusability and maintainability of object-oriented programs.