OC類的加載

上篇我們?cè)?a href="http://www.reibang.com/p/71345d337ae6" target="_blank">dyld分析分析中了解到在dyld啟動(dòng)過(guò)程是會(huì)調(diào)用objc的init方法吗冤,而該init方法中會(huì)往dyld中注冊(cè)一個(gè)回調(diào)腌且,在dyld后續(xù)流程中會(huì)調(diào)用該回調(diào)map_images廉赔。

類的讀取

map_images中主要是調(diào)用map_images_nolock品追,而map_images_nolock主要流程是進(jìn)行了一些初始化,以及最關(guān)鍵的把所有的在讀取到內(nèi)存中來(lái)(注意讀取的類并未實(shí)現(xiàn)衅斩,其ro襟沮、rw數(shù)據(jù)還沒有處理好)

void
map_images(unsigned count, const char * const paths[],
          const struct mach_header * const mhdrs[])
{
   mutex_locker_t lock(runtimeLock);
   return map_images_nolock(count, paths, mhdrs);
}
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                 const struct mach_header * const mhdrs[])
{
   static bool firstTime = YES;
   header_info *hList[mhCount];
   uint32_t hCount;
   size_t selrefCount = 0;

   // Perform first-time initialization if necessary.
   // This function is called before ordinary library initializers. 
   // fixme defer initialization until an objc-using image is found?
   if (firstTime) {
       preopt_init();
   }

   if (PrintImages) {
       _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
   }


   // Find all images with Objective-C metadata.
   hCount = 0;

   // Count classes. Size various table based on the total.
   int totalClasses = 0;
   int unoptimizedTotalClasses = 0;
   {
       uint32_t i = mhCount;
       while (i--) {
           const headerType *mhdr = (const headerType *)mhdrs[i];
           auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
           if (!hi) {
               // no objc data in this entry
               continue;
           }
           
           if (mhdr->filetype == MH_EXECUTE) {
               // Size some data structures based on main executable's size
#if __OBJC2__
               size_t count;
               _getObjc2SelectorRefs(hi, &count);
               selrefCount += count;
               _getObjc2MessageRefs(hi, &count);
               selrefCount += count;
#else
               _getObjcSelectorRefs(hi, &selrefCount);
#endif
               
#if SUPPORT_GC_COMPAT
               // Halt if this is a GC app.
               if (shouldRejectGCApp(hi)) {
                   _objc_fatal_with_reason
                       (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                        OS_REASON_FLAG_CONSISTENT_FAILURE, 
                        "Objective-C garbage collection " 
                        "is no longer supported.");
               }
#endif
           }
           
           hList[hCount++] = hi;
           
           if (PrintImages) {
               _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                            hi->fname(),
                            mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                            hi->info()->isReplacement() ? " (replacement)" : "",
                            hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                            hi->info()->optimizedByDyld()?" (preoptimized)":"");
           }
       }
   }

   // Perform one-time runtime initialization that must be deferred until 
   // the executable itself is found. This needs to be done before 
   // further initialization.
   // (The executable may not be present in this infoList if the 
   // executable does not contain Objective-C code but Objective-C 
   // is dynamically loaded later.
   if (firstTime) {
       //進(jìn)行全局sel集合的初始化
       sel_init(selrefCount);
       //進(jìn)行全局的自動(dòng)釋放池、關(guān)聯(lián)對(duì)象表等缀、弱引用表枷莉、引用計(jì)數(shù)表的初始化
       arr_init();

#if SUPPORT_GC_COMPAT
       // Reject any GC images linked to the main executable.
       // We already rejected the app itself above.
       // Images loaded after launch will be rejected by dyld.

       for (uint32_t i = 0; i < hCount; i++) {
           auto hi = hList[i];
           auto mh = hi->mhdr();
           if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
               _objc_fatal_with_reason
                   (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                    OS_REASON_FLAG_CONSISTENT_FAILURE, 
                    "%s requires Objective-C garbage collection "
                    "which is no longer supported.", hi->fname());
           }
       }
#endif

#if TARGET_OS_OSX
       // Disable +initialize fork safety if the app is too old (< 10.13).
       // Disable +initialize fork safety if the app has a
       //   __DATA,__objc_fork_ok section.

       if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) {
           DisableInitializeForkSafety = true;
           if (PrintInitializing) {
               _objc_inform("INITIALIZE: disabling +initialize fork "
                            "safety enforcement because the app is "
                            "too old (SDK version " SDK_FORMAT ")",
                            FORMAT_SDK(dyld_get_program_sdk_version()));
           }
       }

       for (uint32_t i = 0; i < hCount; i++) {
           auto hi = hList[i];
           auto mh = hi->mhdr();
           if (mh->filetype != MH_EXECUTE) continue;
           unsigned long size;
           if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
               DisableInitializeForkSafety = true;
               if (PrintInitializing) {
                   _objc_inform("INITIALIZE: disabling +initialize fork "
                                "safety enforcement because the app has "
                                "a __DATA,__objc_fork_ok section");
               }
           }
           break;  // assume only one MH_EXECUTE image
       }
#endif

   }

   if (hCount > 0) {
       //讀取所有類
       _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
   }

   firstTime = NO;
   
   // Call image load funcs after everything is set up.
   for (auto func : loadImageFuncs) {
       for (uint32_t i = 0; i < mhCount; i++) {
           func(mhdrs[i]);
       }
   }
}

看一下最關(guān)鍵的_read_images,主要邏輯是:條件控制只執(zhí)行一次尺迂、處理編譯階段SEL混亂的問(wèn)題笤妙、錯(cuò)誤的類處理、加載協(xié)議噪裕、分類處理蹲盘、類的讀取、以及針對(duì)某些非懶加載的在直接進(jìn)行實(shí)現(xiàn)膳音。

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
   header_info *hi;
   uint32_t hIndex;
   size_t count;
   size_t i;
   Class *resolvedFutureClasses = nil;
   size_t resolvedFutureClassCount = 0;
   static bool doneOnce;
   bool launchTime = NO;
   TimeLogger ts(PrintImageTimes);

   runtimeLock.assertLocked();

#define EACH_HEADER \
   hIndex = 0;         \
   hIndex < hCount && (hi = hList[hIndex]); \
   hIndex++

   if (!doneOnce) {
       doneOnce = YES;
       launchTime = YES;

#if SUPPORT_NONPOINTER_ISA
       // Disable non-pointer isa under some conditions.

# if SUPPORT_INDEXED_ISA
       // Disable nonpointer isa if any image contains old Swift code
       for (EACH_HEADER) {
           if (hi->info()->containsSwift()  &&
               hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
           {
               DisableNonpointerIsa = true;
               if (PrintRawIsa) {
                   _objc_inform("RAW ISA: disabling non-pointer isa because "
                                "the app or a framework contains Swift code "
                                "older than Swift 3.0");
               }
               break;
           }
       }
# endif

# if TARGET_OS_OSX
       // Disable non-pointer isa if the app is too old
       // (linked before OS X 10.11)
       if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
           DisableNonpointerIsa = true;
           if (PrintRawIsa) {
               _objc_inform("RAW ISA: disabling non-pointer isa because "
                            "the app is too old (SDK version " SDK_FORMAT ")",
                            FORMAT_SDK(dyld_get_program_sdk_version()));
           }
       }

       // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
       // New apps that load old extensions may need this.
       for (EACH_HEADER) {
           if (hi->mhdr()->filetype != MH_EXECUTE) continue;
           unsigned long size;
           if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
               DisableNonpointerIsa = true;
               if (PrintRawIsa) {
                   _objc_inform("RAW ISA: disabling non-pointer isa because "
                                "the app has a __DATA,__objc_rawisa section");
               }
           }
           break;  // assume only one MH_EXECUTE image
       }
# endif

#endif

       if (DisableTaggedPointers) {
           disableTaggedPointers();
       }
       
       initializeTaggedPointerObfuscator();

       if (PrintConnecting) {
           _objc_inform("CLASS: found %d classes during launch", totalClasses);
       }

       // namedClasses
       // Preoptimized classes don't go in this table.
       // 4/3 is NXMapTable's load factor
       int namedClassesSize = 
           (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
       gdb_objc_realized_classes =
           NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

       ts.log("IMAGE TIMES: first time tasks");
   }

   // Fix up @selector references
   static size_t UnfixedSelectors;
   {
       mutex_locker_t lock(selLock);
       for (EACH_HEADER) {
           if (hi->hasPreoptimizedSelectors()) continue;

           bool isBundle = hi->isBundle();
           SEL *sels = _getObjc2SelectorRefs(hi, &count);
           UnfixedSelectors += count;
           for (i = 0; i < count; i++) {
               const char *name = sel_cname(sels[i]);
               SEL sel = sel_registerNameNoLock(name, isBundle);
               if (sels[i] != sel) {
                   sels[i] = sel;
               }
           }
       }
   }

   ts.log("IMAGE TIMES: fix up selector references");

   // Discover classes. Fix up unresolved future classes. Mark bundle classes.
   bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

   for (EACH_HEADER) {
       if (! mustReadClasses(hi, hasDyldRoots)) {
           // Image is sufficiently optimized that we need not call readClass()
           continue;
       }

       classref_t const *classlist = _getObjc2ClassList(hi, &count);

       bool headerIsBundle = hi->isBundle();
       bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

       for (i = 0; i < count; i++) {
           Class cls = (Class)classlist[i];
           Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

           if (newCls != cls  &&  newCls) {
               // Class was moved but not deleted. Currently this occurs 
               // only when the new class resolved a future class.
               // Non-lazily realize the class below.
               resolvedFutureClasses = (Class *)
                   realloc(resolvedFutureClasses, 
                           (resolvedFutureClassCount+1) * sizeof(Class));
               resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
           }
       }
   }

   ts.log("IMAGE TIMES: discover classes");

   // Fix up remapped classes
   // Class list and nonlazy class list remain unremapped.
   // Class refs and super refs are remapped for message dispatching.
   
   if (!noClassesRemapped()) {
       for (EACH_HEADER) {
           Class *classrefs = _getObjc2ClassRefs(hi, &count);
           for (i = 0; i < count; i++) {
               remapClassRef(&classrefs[i]);
           }
           // fixme why doesn't test future1 catch the absence of this?
           classrefs = _getObjc2SuperRefs(hi, &count);
           for (i = 0; i < count; i++) {
               remapClassRef(&classrefs[i]);
           }
       }
   }

   ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
   // Fix up old objc_msgSend_fixup call sites
   for (EACH_HEADER) {
       message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
       if (count == 0) continue;

       if (PrintVtables) {
           _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                        "call sites in %s", count, hi->fname());
       }
       for (i = 0; i < count; i++) {
           fixupMessageRef(refs+i);
       }
   }

   ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

   bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();

   // Discover protocols. Fix up protocol refs.
   for (EACH_HEADER) {
       extern objc_class OBJC_CLASS_$_Protocol;
       Class cls = (Class)&OBJC_CLASS_$_Protocol;
       ASSERT(cls);
       NXMapTable *protocol_map = protocols();
       bool isPreoptimized = hi->hasPreoptimizedProtocols();

       // Skip reading protocols if this is an image from the shared cache
       // and we support roots
       // Note, after launch we do need to walk the protocol as the protocol
       // in the shared cache is marked with isCanonical() and that may not
       // be true if some non-shared cache binary was chosen as the canonical
       // definition
       if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
           if (PrintProtocols) {
               _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                            hi->fname());
           }
           continue;
       }

       bool isBundle = hi->isBundle();

       protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
       for (i = 0; i < count; i++) {
           readProtocol(protolist[i], cls, protocol_map, 
                        isPreoptimized, isBundle);
       }
   }

   ts.log("IMAGE TIMES: discover protocols");

   // Fix up @protocol references
   // Preoptimized images may have the right 
   // answer already but we don't know for sure.
   for (EACH_HEADER) {
       // At launch time, we know preoptimized image refs are pointing at the
       // shared cache definition of a protocol.  We can skip the check on
       // launch, but have to visit @protocol refs for shared cache images
       // loaded later.
       if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
           continue;
       protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
       for (i = 0; i < count; i++) {
           remapProtocolRef(&protolist[i]);
       }
   }

   ts.log("IMAGE TIMES: fix up @protocol references");

   // Discover categories. Only do this after the initial category
   // attachment has been done. For categories present at startup,
   // discovery is deferred until the first load_images call after
   // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
   if (didInitialAttachCategories) {
       for (EACH_HEADER) {
           load_categories_nolock(hi);
       }
   }

   ts.log("IMAGE TIMES: discover categories");

   // Category discovery MUST BE Late to avoid potential races
   // when other threads call the new category code before
   // this thread finishes its fixups.

   // +load handled by prepare_load_methods()

   // Realize non-lazy classes (for +load methods and static instances)
   for (EACH_HEADER) {
       classref_t const *classlist = 
           _getObjc2NonlazyClassList(hi, &count);
       for (i = 0; i < count; i++) {
           Class cls = remapClass(classlist[i]);
           if (!cls) continue;

           addClassTableEntry(cls);

           if (cls->isSwiftStable()) {
               if (cls->swiftMetadataInitializer()) {
                   _objc_fatal("Swift class %s with a metadata initializer "
                               "is not allowed to be non-lazy",
                               cls->nameForLogging());
               }
               // fixme also disallow relocatable classes
               // We can't disallow all Swift classes because of
               // classes like Swift.__EmptyArrayStorage
           }
           //對(duì)于非懶加載的在召衔,其實(shí)實(shí)現(xiàn)它,具體怎么實(shí)現(xiàn)呢祭陷,看后面
           realizeClassWithoutSwift(cls, nil);
       }
   }

   ts.log("IMAGE TIMES: realize non-lazy classes");

   // Realize newly-resolved future classes, in case CF manipulates them
   if (resolvedFutureClasses) {
       for (i = 0; i < resolvedFutureClassCount; i++) {
           Class cls = resolvedFutureClasses[i];
           if (cls->isSwiftStable()) {
               _objc_fatal("Swift class is not allowed to be future");
           }
           realizeClassWithoutSwift(cls, nil);
           cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
       }
       free(resolvedFutureClasses);
   }

   ts.log("IMAGE TIMES: realize future classes");

   if (DebugNonFragileIvars) {
       realizeAllClasses();
   }


   // Print preoptimization statistics
   if (PrintPreopt) {
       static unsigned int PreoptTotalMethodLists;
       static unsigned int PreoptOptimizedMethodLists;
       static unsigned int PreoptTotalClasses;
       static unsigned int PreoptOptimizedClasses;

       for (EACH_HEADER) {
           if (hi->hasPreoptimizedSelectors()) {
               _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                            "in %s", hi->fname());
           }
           else if (hi->info()->optimizedByDyld()) {
               _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                            "in %s", hi->fname());
           }

           classref_t const *classlist = _getObjc2ClassList(hi, &count);
           for (i = 0; i < count; i++) {
               Class cls = remapClass(classlist[i]);
               if (!cls) continue;

               PreoptTotalClasses++;
               if (hi->hasPreoptimizedClasses()) {
                   PreoptOptimizedClasses++;
               }
               
               const method_list_t *mlist;
               if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
                   PreoptTotalMethodLists++;
                   if (mlist->isFixedUp()) {
                       PreoptOptimizedMethodLists++;
                   }
               }
               if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
                   PreoptTotalMethodLists++;
                   if (mlist->isFixedUp()) {
                       PreoptOptimizedMethodLists++;
                   }
               }
           }
       }

       _objc_inform("PREOPTIMIZATION: %zu selector references not "
                    "pre-optimized", UnfixedSelectors);
       _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                    PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                    PreoptTotalMethodLists
                    ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                    : 0.0);
       _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                    PreoptOptimizedClasses, PreoptTotalClasses, 
                    PreoptTotalClasses 
                    ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                    : 0.0);
       _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                    "pre-optimized", UnfixedProtocolReferences);
   }

#undef EACH_HEADER
}

再看一下最關(guān)鍵的readClass苍凛,主要就是把類讀取到全局的classTable中來(lái)

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{    
   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->superclass = nil;
       return nil;
   }
   
   cls->fixupBackwardDeployingStableSwift();

   Class replacing = nil;
   if (Class newCls = popFutureNamedClass(mangledName)) {
       // This name was previously 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));
       rw->set_ro((class_ro_t *)newCls->data());
       newCls->setData(rw);
       freeIfMutable((char *)old_ro->name);
       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(getClassExceptSomeSwift(mangledName));
   } else {
       addNamedClass(cls, mangledName, replacing);
       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;
}

梳理一下類的讀取流程:map_images->map_images_nolock->_read_images->readClass->addClassTableEntry,就是讀取images中所有的類并把它們加載到全局的classTable中來(lái)兵志。

類的實(shí)現(xiàn)

另外在read_images過(guò)程中對(duì)于非懶加載的類(實(shí)現(xiàn)過(guò)load方法的類)會(huì)直接對(duì)其實(shí)現(xiàn)醇蝴,進(jìn)入realizeClassWithoutSwift方法中來(lái)。而該方法主要是處理類以及其父類元類的關(guān)系想罕,加載方法列表(包括分類的方法)悠栓。

static Class realizeClassWithoutSwift(Class cls, Class previously)
{
   runtimeLock.assertLocked();

   class_rw_t *rw;
   Class supercls;
   Class metacls;
   
   if (!cls) return nil;
   if (cls->isRealized()) return cls;
   ASSERT(cls == remapClass(cls));

   // fixme verify class is not in an un-dlopened part of the shared cache?

   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 {
       // Normal class. Allocate writeable class data.
       //申請(qǐng)rw空間大小的內(nèi)存
       rw = objc::zalloc<class_rw_t>();
       //填充其數(shù)據(jù)為ro
       rw->set_ro(ro);
       rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
       //設(shè)置類的rw信息!0醇邸惭适!
       cls->setData(rw);
   }

#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)" : "");
   }

   //獲取其父類和元類信息,建立oc類楼镐、父類癞志、元類三者之間的關(guān)系
   supercls = realizeClassWithoutSwift(remapClass(cls->superclass), 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->name, "OS_object"))
       {
           // hack for libdispatch et al - isa also acts as vtable pointer
           hackedDispatch = true;
           instancesRequireRawIsa = true;
       }
       else if (supercls  &&  supercls->superclass  &&
                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->superclass = 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 categories
   //加載方法列表,以及分類數(shù)據(jù)
   methodizeClass(cls, previously);

   return cls;
}

分類的本質(zhì)

在進(jìn)入methodizeClass方法前我們先看一下分類的本質(zhì)是什么框产。最核心的是被轉(zhuǎn)換成了_category_t這個(gè)一個(gè)結(jié)構(gòu)體今阳。

//定義一個(gè)LGPerson的分類师溅,增加一個(gè)類方法和實(shí)例方法以及一個(gè)屬性
@interface LGPerson(AA)
@property (nonatomic, copy) NSString *categoryNickName;

- (void)lg_categoryInstanceMethod;
+ (void)lg_categoryClassMethod;
@end

@implementation LGPerson(AA)
- (void)lg_categoryInstanceMethod{

}
+ (void)lg_categoryClassMethod {

}
@end

然后用 clang -rewrite-objc main.m -o main.cpp 看一下在編譯階段真正的實(shí)現(xiàn)

//聲明并初始化了一個(gè)類型為_category_t的全局靜態(tài)變量
static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_AA __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
   "LGPerson",
   0, // &OBJC_CLASS_$_LGPerson,
   (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_AA,
   (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_AA,
   0,
   (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson_$_AA,
};

//看一下_category_t是什么
struct _category_t {
   const char *name;//類名稱
   struct _class_t *cls;//類
   const struct _method_list_t *instance_methods;//實(shí)例方法列表
   const struct _method_list_t *class_methods;//類方法列表
   const struct _protocol_list_t *protocols;//協(xié)議列表
   const struct _prop_list_t *properties;//屬性列表
};

static struct /*_method_list_t*/ {
   unsigned int entsize;  // sizeof(struct _objc_method)
   unsigned int method_count;
   struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_AA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
   sizeof(_objc_method),
   1,
   {{(struct objc_selector *)"lg_categoryInstanceMethod", "v16@0:8", (void *)_I_LGPerson_AA_lg_categoryInstanceMethod}}
};

static struct /*_method_list_t*/ {
   unsigned int entsize;  // sizeof(struct _objc_method)
   unsigned int method_count;
   struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_AA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
   sizeof(_objc_method),
   1,
   {{(struct objc_selector *)"lg_categoryClassMethod", "v16@0:8", (void *)_C_LGPerson_AA_lg_categoryClassMethod}}
};

static struct /*_prop_list_t*/ {
   unsigned int entsize;  // sizeof(struct _prop_t)
   unsigned int count_of_properties;
   struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_LGPerson_$_AA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
   sizeof(_prop_t),
   1,
   {{"categoryNickName","T@\"NSString\",C,N"}}
};

可以看到在底層分類被轉(zhuǎn)換成了_category_t這個(gè)結(jié)構(gòu)體,里面存儲(chǔ)了所有的方法和屬性盾舌,但屬性沒有g(shù)et墓臭、set方法。

方法的處理

那接下來(lái)我們看一下methodizeClass中是如何處理類的方法以及分類方法的妖谴。主要是從ro中取出baseMethods窿锉,然后進(jìn)行排序,如果需要處理分類(當(dāng)分類中實(shí)現(xiàn)了load方法)膝舅,則會(huì)把分類數(shù)據(jù)attach到整個(gè)方法列表中來(lái)嗡载。

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) {
       //對(duì)方法進(jìn)行排序
       prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
       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);
       }
   }
   //該方法會(huì)嘗試加載分類方法,但內(nèi)部有條件
   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
}

看一下attachToClass的實(shí)現(xiàn)仍稀,該方法主要是主類沒有l(wèi)oad方法洼滚,分類有l(wèi)oad方法時(shí)進(jìn)行分類方法的attach。

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);
       if (it != map.end()) {
           //只有主類沒load技潘,分類有l(wèi)oad時(shí)條件才成立
           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);
       }
   }

看一下最attachCategories實(shí)現(xiàn)遥巴,主要邏輯是先創(chuàng)建rwe,然后遍歷所有的分類方法享幽,然后一個(gè)一個(gè)attach到類的methodlist中來(lái)铲掐,并排好序

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)" : "");
   }

   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);
   //創(chuàng)建rwe
   auto rwe = cls->data()->extAllocIfNeeded();
   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) {
               prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
               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);
       rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
       if (flags & ATTACH_EXISTING) flushCaches(cls);
   }

   rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

   rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

看一下attachlists方法,該方法分三種情況值桩,如果只有一個(gè)list摆霉,那整個(gè)方法列表將是一個(gè)單列表,如果從一個(gè)變多個(gè)list奔坟,那將重新分配內(nèi)存就成二維數(shù)組携栋,如果本身就是二維的繼續(xù)往里面插,則進(jìn)行內(nèi)存拷貝和移動(dòng)咳秉。對(duì)于新的list是放前面的婉支,所以分類的方法在查找時(shí)先被找到。

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }

其實(shí)跟蹤attachCategories的調(diào)用情況滴某,可以看到還有這樣一條調(diào)用鏈路:
load_images->loadAllCategories->load_categories_nolock->attachCategories
那什么情況下會(huì)走到該流程中呢磅摹,和上面我們分析的分類方法的處理流程又有什么不同呢滋迈?
其實(shí)主要是根據(jù)主類和分類是否有l(wèi)oad方法霎奢,會(huì)有不同的邏輯處理,分4種情況:

  • 主類有l(wèi)oad饼灿,分類有l(wèi)oad幕侠,分類的加載就是load_images流程中,此時(shí)會(huì)產(chǎn)生rwe碍彭。
  • 主類有l(wèi)oad晤硕,分類沒load悼潭,主類進(jìn)入非懶加載流程提交實(shí)現(xiàn),在主類加載的時(shí)候會(huì)把主類中的方法和分類中的方法一并加載進(jìn)來(lái)舞箍,所以此時(shí)不會(huì)產(chǎn)生rwe舰褪。
  • 主類沒load,分類有l(wèi)oad疏橄,在load_images方法的prepare_load_methods流程中會(huì)加載所有分類中的load方法占拍,此時(shí)會(huì)調(diào)用主類的實(shí)現(xiàn)方法,而在其實(shí)現(xiàn)方法中會(huì)調(diào)用attachCategories加載分類捎迫。此時(shí)會(huì)產(chǎn)生rwe晃酒。
  • 主類沒load,分類沒load窄绒,主類的實(shí)現(xiàn)在第一次消息發(fā)送的時(shí)候贝次,然后會(huì)把主類和分類中的方法都一次性加載進(jìn)來(lái),此時(shí)不會(huì)產(chǎn)生rwe彰导。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛔翅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子螺戳,更是在濱河造成了極大的恐慌搁宾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倔幼,死亡現(xiàn)場(chǎng)離奇詭異盖腿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)损同,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門翩腐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人膏燃,你說(shuō)我怎么就攤上這事茂卦。” “怎么了组哩?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵等龙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我伶贰,道長(zhǎng)蛛砰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任黍衙,我火速辦了婚禮泥畅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘琅翻。我一直安慰自己位仁,他們只是感情好柑贞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著聂抢,像睡著了一般钧嘶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上琳疏,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天康辑,我揣著相機(jī)與錄音,去河邊找鬼轿亮。 笑死疮薇,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的我注。 我是一名探鬼主播按咒,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼但骨!你這毒婦竟也來(lái)了励七?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤奔缠,失蹤者是張志新(化名)和其女友劉穎掠抬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體校哎,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡两波,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闷哆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腰奋。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖抱怔,靈堂內(nèi)的尸體忽然破棺而出劣坊,到底是詐尸還是另有隱情,我是刑警寧澤屈留,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布局冰,位于F島的核電站,受9級(jí)特大地震影響灌危,放射性物質(zhì)發(fā)生泄漏康二。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一乍狐、第九天 我趴在偏房一處隱蔽的房頂上張望赠摇。 院中可真熱鬧固逗,春花似錦浅蚪、人聲如沸藕帜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洽故。三九已至,卻和暖如春盗誊,著一層夾襖步出監(jiān)牢的瞬間时甚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工哈踱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荒适,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓开镣,卻偏偏與公主長(zhǎng)得像刀诬,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邪财,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 引言 通過(guò)上文ios應(yīng)用程序的加載流程[http://www.reibang.com/p/4dd544cc2c3...
    浪的出名閱讀 468評(píng)論 0 0
  • 引言 通過(guò)上文OC類的加載上[http://www.reibang.com/p/84b8f2545b25]知道加...
    浪的出名閱讀 314評(píng)論 0 0
  • iOS--OC底層原理文章匯總[/p/14911da92f74] 本文繼續(xù)研究類的加載--分類(類別)陕壹。 分類Ca...
    夏天的楓_閱讀 259評(píng)論 0 1
  • 久違的晴天,家長(zhǎng)會(huì)树埠。 家長(zhǎng)大會(huì)開好到教室時(shí)糠馆,離放學(xué)已經(jīng)沒多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)怎憋。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評(píng)論 16 22
  • 今天感恩節(jié)哎又碌,感謝一直在我身邊的親朋好友。感恩相遇绊袋!感恩不離不棄赠橙。 中午開了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,564評(píng)論 0 11