OC类的加载

2020-12-06  本文已影响0人  会跑的鱼_09

上篇我们在dyld分析分析中了解到在dyld启动过程是会调用objc的init方法,而该init方法中会往dyld中注册一个回调,在dyld后续流程中会调用该回调map_images。

类的读取

map_images中主要是调用map_images_nolock,而map_images_nolock主要流程是进行了一些初始化,以及最关键的把所有的在读取到内存中来(注意读取的类并未实现,其ro、rw数据还没有处理好)

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) {
       //进行全局sel集合的初始化
       sel_init(selrefCount);
       //进行全局的自动释放池、关联对象表、弱引用表、引用计数表的初始化
       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]);
       }
   }
}

看一下最关键的_read_images,主要逻辑是:条件控制只执行一次、处理编译阶段SEL混乱的问题、错误的类处理、加载协议、分类处理、类的读取、以及针对某些非懒加载的在直接进行实现。

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
           }
           //对于非懒加载的在,其实实现它,具体怎么实现呢,看后面
           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
}

再看一下最关键的readClass,主要就是把类读取到全局的classTable中来

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中来。

类的实现

另外在read_images过程中对于非懒加载的类(实现过load方法的类)会直接对其实现,进入realizeClassWithoutSwift方法中来。而该方法主要是处理类以及其父类元类的关系,加载方法列表(包括分类的方法)。

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.
       //申请rw空间大小的内存
       rw = objc::zalloc<class_rw_t>();
       //填充其数据为ro
       rw->set_ro(ro);
       rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
       //设置类的rw信息!!!
       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类、父类、元类三者之间的关系
   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
   //加载方法列表,以及分类数据
   methodizeClass(cls, previously);

   return cls;
}

分类的本质

在进入methodizeClass方法前我们先看一下分类的本质是什么。最核心的是被转换成了_category_t这个一个结构体。

//定义一个LGPerson的分类,增加一个类方法和实例方法以及一个属性
@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 看一下在编译阶段真正的实现

//声明并初始化了一个类型为_category_t的全局静态变量
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;//实例方法列表
   const struct _method_list_t *class_methods;//类方法列表
   const struct _protocol_list_t *protocols;//协议列表
   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"}}
};

可以看到在底层分类被转换成了_category_t这个结构体,里面存储了所有的方法和属性,但属性没有get、set方法。

方法的处理

那接下来我们看一下methodizeClass中是如何处理类的方法以及分类方法的。主要是从ro中取出baseMethods,然后进行排序,如果需要处理分类(当分类中实现了load方法),则会把分类数据attach到整个方法列表中来。

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

看一下attachToClass的实现,该方法主要是主类没有load方法,分类有load方法时进行分类方法的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,分类有load时条件才成立
           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实现,主要逻辑是先创建rwe,然后遍历所有的分类方法,然后一个一个attach到类的methodlist中来,并排好序

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);
   //创建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方法,该方法分三种情况,如果只有一个list,那整个方法列表将是一个单列表,如果从一个变多个list,那将重新分配内存就成二维数组,如果本身就是二维的继续往里面插,则进行内存拷贝和移动。对于新的list是放前面的,所以分类的方法在查找时先被找到。

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]));
        }
    }

其实跟踪attachCategories的调用情况,可以看到还有这样一条调用链路:
load_images->loadAllCategories->load_categories_nolock->attachCategories
那什么情况下会走到该流程中呢,和上面我们分析的分类方法的处理流程又有什么不同呢?
其实主要是根据主类和分类是否有load方法,会有不同的逻辑处理,分4种情况:

上一篇下一篇

猜你喜欢

热点阅读