This chapter content
- Class implementation source code parsing
- Lazy-loaded classes versus non-lazy-loaded classes
- Analysis of rW, RO, RWE in class structure
This ZhangMu
Knowing when classes are loaded also helps us with startup optimizations, and knowing what makes rW,ro, and RWE different and why, and when RWE is assigned, helps us understand the runtime mechanism. In terms of class loading, we’re talking about the implementation of the class in this article (which is also true of class loading), and why we’re talking about that will come later.
The realization of the class
We all know that we have OC/(Swift, which itself has no Runtime, but they both share objc, but the Swift class and OC implementation are different). It differs from other languages because at runtime we can change the structure of the class, the size of memory, put what needs to be done at compile time into runtime, and so on. But a programming language is different from other languages but it certainly has things in common, things that we already know at compile time, things like class member variables, names, things like that. And for the implementation of the class we also need to use the method analysis that we saw with the implementation of the class in the objC initialization.
Tip: If you have any questions about ro, RW, and RWE in this section, read my explanation below and then read this section
ReadClass analysis
As you can see from my description of read_images in the last chapter, 3 processes can give you a general idea of what’s going on in this function with respect to the processing of the class. It reads the list of classes from Mach-o and adds names to each class, adding them to the global class table we mentioned earlier. This operation will happen to both the system class and our own creation
Class readClass(Class CLS, bool headerIsBundle, bool headerIsPreoptimized) { Const char *mangledName = CLS ->nonlazyMangledName(); if (missingWeakSuperclass(cls)) { // No superclass (probably weak-linked). // Disavow any knowledge of this subclass. if (PrintConnecting) { _objc_inform("CLASS: IGNORING class '%s' with " "missing weak-linked superclass", cls->nameForLogging()); } addRemappedClass(cls, nil); cls->setSuperclass(nil); return nil; } cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (mangledName ! = nullptr) {// Do not assume that the ro assignment to rw in the class implementation is here. // Can self-verify, If (Class newCls = popFutureNamedClass(mangledName)) {// Previously allocated This name was allocated as a future class. // Copy objc_class to future class's struct. // Preserve future's rw data block. if (newCls->isAnySwift()) { _objc_fatal("Can't complete future class request for '%s' " "because the real class is too big.", cls->nameForLogging()); } class_rw_t *rw = newCls->data(); const class_ro_t *old_ro = rw->ro(); memcpy(newCls, cls, sizeof(objc_class)); // Manually set address-discriminated ptrauthed fields // so that newCls gets the correct signatures. newCls->setSuperclass(cls->getSuperclass()); newCls->initIsa(cls->getIsa()); rw->set_ro((class_ro_t *)newCls->data()); newCls->setData(rw); freeIfMutable((char *)old_ro->getName()); free((void *)old_ro); addRemappedClass(cls, newCls); replacing = cls; cls = newCls; } } if (headerIsPreoptimized && ! replacing) { // class list built in shared cache // fixme strict assert doesn't work because of duplicates // ASSERT(cls == getClass(name)); ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName)); } else {if (mangledName) {//some Swift generic classes can lazily generate their names addNamedClass(cls, mangledName, replacing); } else { Class meta = cls->ISA(); const class_ro_t *metaRO = meta->bits.safe_ro(); ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass."); ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class."); } // Add the bound class to the global class table addClassTableEntry(CLS); } // for future reference: shared cache never contains MH_BUNDLEs if (headerIsBundle) { cls->data()->flags |= RO_FROM_BUNDLE; cls->ISA()->data()->flags |= RO_FROM_BUNDLE; } return cls; }Copy the code
The realizeClassWithoutSwift class implements the core methods
This method is the core method for the class implementation, and any class (lazy-loaded, non-lazy-loaded) must be implemented by this method. At this point, you may still be vague about the class loading, feel that there is no definitive answer, don’t worry, first look at the source code, analysis. The source code is recommended to go through, although not necessarily remember, but it is good to know what you do.
One of the most critical is the assignment to RW and so on, and this class implementation, with metaclass, superclass, superclass metaclass and so on are implemented
static Class realizeClassWithoutSwift(Class cls, Class previously) { runtimeLock.assertLocked(); class_rw_t *rw; Class supercls; Class metacls; if (! cls) return nil; / / if the class has come true is returned, proving that the process error, there is a way to assert the 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? // Get ro, which stands for pointer cast, which you can use as a coercion type. After all, what you read from Mach-o is just a string of numbers. // But here you might wonder: ro = rw, the structure must be strongly converted to ensure that the type is consistent. Auto ro = (const class_ro_t *) CLS ->data(); Auto isMeta = ro->flags & RO_META; // If it is futer class, Of allocated tokens if (ro->flags & RO_FUTURE) {// This was a future class.rw data is already allocated cls->data(); 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->set_ro(ro); / / identity class implements rw - > flags = RW_REALIZED | RW_REALIZING | isMeta; CLS ->setData(rw); } / / this is a class cache initialization, news find that cache_t 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. // For ObjC subclasses of Swift classes. Supercls = realizeClassWithoutSwift(CLS ->getSuperclass()), nil); Metacls = realizeClassWithoutSwift(remapClass(CLS ->ISA()), nil); / / nonpointer_isa, #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. // We see that isa print for metaclass is the name of its class, Because here the 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; (OBJC_DISABLE_NONPOINTER_ISA) (OBJC_DISABLE_NONPOINTER_ISA) (OBJC_DISABLE_NONPOINTER_ISA) 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; } // See here to set according to the environment variable. Don't need to care about the if (instancesRequireRawIsa) {CLS - > setInstancesRequireRawIsaRecursively (rawIsaIsInherited); } // SUPPORT_NONPOINTER_ISA #endif // Update superclass and metaclass in case of remapping // Set superclass, 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; If (supercls) {addSubclass(supercls, CLS);} // Connect this class to its superclass's subclass lists. } else { addRootClass(cls); } // Attach categories // MethodizeClass (CLS, previously); return cls; }Copy the code
MethodizeClass analysis
Remember that the PREVIOUSLY parameter is meaningless, so don’t worry about it. We have already assigned to the rW of the class, so the following method is to handle the protocol, attributes, methods, etc., see the source code for what to do.
In fact, we can see that in addition to sorting methods, it is mainly for the RWE case, and what rWE is, when it is created, and what conditions are created. We can actually guess a few things from this method, like properties, methods, protocols what did we do to cause it to be created, but also the classification, right
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. // Retrieve the methods that ro implements at compile time. method_list_t *list = ro->baseMethods(); PrepareMethodLists (CLS, &list, 1, YES, isBundleClass(CLS), nullptr); // Add rWE when it is available. // As an extra tip, the list is actually a pointer to an array, you can think of it as an array pointer, it is multidimensional. If (rwe) rwe->methods. AttachLists (&list, 1); } property_list_t *proplist = ro-> baseproperty_list_t *proplist = ro->baseProperties; if (rwe && proplist) { rwe->properties.attachLists(&proplist, 1); } // Protocol_list_t *protolist = ro->baseProtocols; // Protocol_list_t *protolist = ro->baseProtocols; // 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 If (CLS ->isRootMetaclass()) {// root metaclass addMethod(CLS, @selector()); (IMP)&objc_noop_imp, "", NO); } // Attach categories. // Add nil, {if you don't have to struggle the if (previously) (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); }} // This method is interesting. I don't know if I remember one of the two tables created by runtime_init. // objc::unattachedCategories.init(32); Table 1 / / objc: : allocatedClasses init (); Table 2 / / I'll explain loading classification, we can see here know, class loading and classification load is also has a lot of contact 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
conclusion
After looking at the class implementation, I’m sure you’ll get a rough idea of what the system does for us when the class is loaded, such as assigning values to RW, handling methods, attributes, protocols, classes, etc. But you have to wonder when the class is loaded, how its loading relates to the classification, and how the classification is loaded. I didn’t explain it up there, so let’s read it down
Lazy-loaded classes versus non-lazy-loaded classes
Why is there such a distinction between what is a non-lazy loaded class and what is a lazy loaded class. Let me just say the answer first
Lazy loading: Implemented when a class first triggers a message to be sent. Do you find in the news sent slow process realizeAndInitializeIfNeeded_locked such a method. It implements classes.
Non-lazily loaded classes: Classes or their classes implement the load method, which is implemented before pre-main. That is, flow through the map_images function.
conclusion
Actually, it’s a little early to talk about class loading, because we’re only looking at classes, and we’re not looking at classification. Because classification also affects the implementation of classes. But here’s a quick, though incomplete, summary:
The ro of the class is defined at compile time, so you can think of the RO as class loading, but the actual implementation of the class is that it’s loaded into memory and it’s used by us. When the load method is pre-main it’s loaded before it’s loaded and includes its parent class, its metaclass, and so on. When the load method is not implemented, the message is loaded when it is first sent.
Why do you do that? I don’t know if you’ve read my previous article on application loading, but if you load unnecessary things in the first place, it will cause slow startup, so it is lazy to load the application, and load one piece at a time, which saves memory and optimizes startup time
rw, ro, rwe
These three constructs are the core of the influence class.
Rw is class_rw_t, dirtyMemory, it contains ro and rWE, that is, we usually use methods, protocols, properties and so on from this, the default class implementation is ro data, remember when rW is created the assignment operation. But why rWE?
Ro is essentially class_RO_t, cleanMemory, which is the clean stuff about the class, which is determined at compile time, like the class name, the member variables, some of the methods, some of the properties, that’s on the disk. So when we look at the previous chapters, class implementations always start with RO because that’s immutable, that’s on the disk.
Class_rw_ext_t is essentially an extension of RW, because at runtime we can dynamically add methods, attributes, protocols, and all sorts of operations that affect the structure of the class, the RW, and so on, and so the system will open up the RWE and create it. However, not every class will have these operations, so these classes will not open up RWE and avoid touching the class structure
The problem
Can registered classes take advantage of runtime to add member variables? What about classes that are not registered?
What is a registered class that has been added to the class table is the category table. Registered classes cannot be added, and unregistered classes can be added without thinking. Why is that? How do we register classes? It’s done through the objc_registerClassPair method, which has an RW_CONSTRUCTING identifier in it, which means the class is being constructed, and what do we do when we add a member variable? Class_addIvar is a method that cannot be added to a class that has already been built