OC类的加载-上

引言

  • 通过上文ios应用程序的加载流程知道dyld后面会执行libobjc的_objc_init方法,本文我们将从_objc_init方法入手研究oc类的加载

_objc_init

  • _objc_init源码实现如下
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
  • environ_init() : 读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助调试。
  • tls_init() 关于线程key的绑定 - 比如每线程数据的析构函数
  • static_init() 运行C ++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init(),因此我们必须自己做
  • runtime_init() : runtime运行时环境初始化,里面主要是:unattachedCategories,allocatedClasses 后面会分析
  • exception_init () 初始化libobjc的异常处理系统
  • cache_init() 缓存条件初始化
  • _imp_implementationWithBlock_init :启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib。
  • 最后就是我们重点研究的两个方法map_imagesload_images

环境变量

  • 我们可以通过终端输入export OBJC_HELP = 1打印出对应的环境变量,和通过修改源码环境条件打印出来的是一样的
......
objc[47651]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[47651]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[47651]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[47651]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
  • 我们可以通过配置环境变量,做到一些特殊的处理,比如设置OBJC_DISABLE_NONPOINTER_ISA为YES,isa就不会被优化,设置OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods打印所有load方法,通过后面的使用说明,也可以知道对应的作用,具体设置环境变量如下
    image.png

map_images

  • map_images映射镜像,内部调用了map_images_nolock方法
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ......
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    ......
}
  • _read_images方法做了很多事情,我们重点研究第3条和第9条
    • 1: 条件控制进行一次的加载,创建了一个mapTable
if (!doneOnce) {
        ......
        // namedClasses
        // Preoptimized classes don't go in this table.
        // 4/3 is NXMapTable's load factor
        // 表的大小
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        // 创建一个上面大小的表用来存放class
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

      // This is a misnomer: gdb_objc_realized_classes is actually a list of 
      // named classes not in the dyld shared cache, whether realized or not.
      //gdb_objc_realized_classes实际上是不在dyld共享缓存中的已命名类的列表,无论是否实现。
      NXMapTable *gdb_objc_realized_classes;  // exported for debuggers in objc-gdb.h
}
  • 2: 修复预编译阶段的 @selector 的混乱问题
// Fix up @selector references
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle();
            // 从mach-o静态段__objc_selrefs获取数据
            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;
                }
            }
        }
    }
  • 3: 错误混乱的类处理
// 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;
        }
        // 从mach-o静态端__objc_classlist获取类列表
        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");
  • 4:修复重映射一些没有被镜像文件加载进来的 类
// 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) {
            // 从mach-o静态端__objc_classrefs获取类的引用
            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?
            // 从mach-o静态端__objc_superrefs获取类的引用
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

    ts.log("IMAGE TIMES: remap classes");
  • 5: 修复一些消息!
 // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        // 从mach-o静态端__objc_msgrefs获取方法引用
        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");
  • 6: 当我们类里面有协议的时候 : readProtocol
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");
  • 7: 修复没有被加载的协议
// 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");
  • 8: 分类处理
// 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
// 发现类别。 仅在完成初始类别附件后才执行此操作。 对于启动时出现的类别,发现将推迟到对_dyld_objc_notify_register的调用完成后的第一个load_images调用为止。

    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    ts.log("IMAGE TIMES: discover categories");
  • 9: 类的加载处理
// 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");
  • 10 : 没有被处理的类 优化那些被侵犯的类
 // 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");

readClass

  • readClass的具体实现,通过lldb调试去掉一些不会进入的代码
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName(); 
    cls->fixupBackwardDeployingStableSwift();
    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 {
        // 以mangledName为key添加class到全局表中
        addNamedClass(cls, mangledName, replacing);
        // 将cls添加到全局表中,如果有元类,将元类也添加进去
        addClassTableEntry(cls);
    }
    return cls;
}

realizeClassWithoutSwift

  • _read_images里面有下面代码,通过注释可以知道这是非懒加载类的情况,系统默认的类都是懒加载的,可以通过实现load方法让它变成非懒加载的类
// 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)
            ......
            realizeClassWithoutSwift(cls, nil);
}
  • realizeClassWithoutSwift是实现类的方法,我们可以通过断点,查看堆栈看看懒加载类是在什么时候实现realizeClassWithoutSwift方法的
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
  * frame #0: 0x000000010039b20e libobjc.A.dylib`realizeClassWithoutSwift(cls=0x00000001000024f8, previously=0x0000000000000000) at objc-runtime-new.mm:2516:9
    frame #1: 0x00000001003b7cb1 libobjc.A.dylib`realizeClassMaybeSwiftMaybeRelock(cls=0x00000001000024f8, lock=0x00000001003f3000, leaveLocked=true) at objc-runtime-new.mm:2782:9
    frame #2: 0x00000001003a33c2 libobjc.A.dylib`realizeClassMaybeSwiftAndLeaveLocked(cls=0x00000001000024f8, lock=0x00000001003f3000) at objc-runtime-new.mm:2805:12
    frame #3: 0x00000001003a30dc libobjc.A.dylib`::lookUpImpOrForward(inst=0x0000000100002520, sel="alloc", cls=0x00000001000024f8, behavior=3) at objc-runtime-new.mm:6165:15
    frame #4: 0x000000010037e859 libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1101
    frame #5: 0x00000001003d95b7 libobjc.A.dylib`::objc_alloc(Class) [inlined] callAlloc(cls=Person, checkNil=true, allocWithZone=false) at NSObject.mm:1714:12
    frame #6: 0x00000001003d9510 libobjc.A.dylib`::objc_alloc(cls=Person) at NSObject.mm:1730
    frame #7: 0x00000001000018ab Test`main(argc=, argv=) + 43 [opt]
    frame #8: 0x00007fff70b037fd libdyld.dylib`start + 1
    frame #9: 0x00007fff70b037fd libdyld.dylib`start + 1
  • 可以发现懒加载的类的realizeClassWithoutSwift会在第一次调用方法时候通过lookUpImpOrForward->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift实现。
  • realizeClassWithoutSwift的具体实现,通过lldb调试去掉一些不会进入的代码
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    ......
    if (!cls) return nil;  // cls为空判断
    if (cls->isRealized()) return cls;// 类已经实现判断
    ASSERT(cls == remapClass(cls));

    auto ro = (const class_ro_t *)cls->data();//从macho文件中获取data强转成ro
    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 = objc::zalloc();//新建一个rw
        rw->set_ro(ro);//设置ro
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);//设置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)" : "");
    }

    // 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.
    // 递归 确定继承链,元类 上面有结束条件
    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;
}
  • 初始化了一些类的信息,ro的赋值,rw的赋值
auto ro = (const class_ro_t *)cls->data();
 rw = objc::zalloc();
 rw->set_ro(ro);
 rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
 cls->setData(rw);
  • 然后是递归确定继承链,元类,一些isa的处理,设置c++方法等
  • 最后调用methodizeClass

methodizeClass

  • methodizeClass方法
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();
    
    const char *mangledName = cls->mangledName();
    const char *personClassName = "Person";
    if (strcmp(mangledName, personClassName) == 0) {
        printf("Person进入methodizeClass方法\n");
    }
    // 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
}
  • 对方法列表进行排序,然后附加到列表中
// 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);//添加到rwe中
    }
  • 实际执行代码的时候发现rwe为NULL,全局搜索rwe的赋值,发现只有在附加分类,addMethod,addMethods,class_addProtocol,_class_addProperty等方法才会执行extAllocIfNeeded的初始化,也就是会对类信息进行动态改变的时候才会初始化,也印证了[苹果wwdc2020]{https://developer.apple.com/videos/play/wwdc2020/10163/}所说的

ro是干净内存,rw是可以通过动态修改的,而在实际使用的类里面统计到只有很少的类会被动态修改,于是就有了rwe,可以大大节省内存

  • prepareMethodLists对方法列表进行排序,fixupMethodList通过selector address进行排序,如果分类里面有相同的方法会根据编译顺序后编译的在前面。
static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name);
            meth.name = sel_registerNameNoLock(name, bundleCopy);
        }
    }

    // Sort by selector address.
    if (sort) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // Mark method list as uniqued and sorted
    mlist->setFixedUp();
}
  • attachCategories通过在分类实现load方法,可以让程序进入断点
    image.png
  • 打印他的调用堆栈
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 8.1
  * frame #0: 0x00000001003b2789 libobjc.A.dylib`attachCategories(cls=Person, cats_list=0x00007ffeefbf6c48, cats_count=1, flags=8) at objc-runtime-new.mm:1311:9
    frame #1: 0x00000001003b4c94 libobjc.A.dylib`load_categories_nolock(this=0x00007ffeefbf6ca0, catlist=0x0000000100002170)::$_4::operator()(category_t* const*) const at objc-runtime-new.mm:3060:25
    frame #2: 0x000000010039ceda libobjc.A.dylib`load_categories_nolock(hi=0x000000010063c110) at objc-runtime-new.mm:3079:5
    frame #3: 0x000000010039bdea libobjc.A.dylib`loadAllCategories() at objc-runtime-new.mm:3087:9
    frame #4: 0x000000010037c816 libobjc.A.dylib`::load_images(path="/private/tmp/objc.dst/usr/lib/libobjc.A.dylib", mh=0x0000000100378000) at objc-runtime-new.mm:3105:9
    frame #5: 0x000000010000c264 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 418
    frame #6: 0x000000010001fdb5 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 475
    frame #7: 0x000000010001de58 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
    frame #8: 0x000000010001de82 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 230
    frame #9: 0x000000010001def8 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
    frame #10: 0x000000010000c66b dyld`dyld::initializeMainExecutable() + 129
    frame #11: 0x0000000100011cd0 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6797
    frame #12: 0x000000010000b227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
    frame #13: 0x000000010000b025 dyld`_dyld_start + 37
  • 发现加载分类的操作是在load_images里面执行的
  • objc::unattachedCategories.attachToClass
  • 看到这个我们发现在_objc_init里面runtime_init的时候有对unattachedCategories的初始化
void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
  • attachCategories分类数据的加载
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
     ......
    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) {//ATTACH_BUFSIZ=64,一般情况下分类的数量不会有这么多
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;//将mlist倒序插入到mlists里面
            fromBundle |= entry.hi->isBundle();
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);//对插入完成的mlists排序
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);//将数据附加到lists中
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }
  ......
}
  • 这里我们只研究方法列表,其他协议列表和属性列表原理都是一样的
  • methods.attachLists增加数据有3种情况
    • 多对多// many lists -> many lists,会先对空间进行扩容,然后将旧数据平移,再将新数据加到最前面
    • 0对1// 0 lists -> 1 list,直接将传进来数组的第一个元素赋值给list
    • 1对多//1 list -> many lists和多对多的处理类似,先扩容然后将旧数据直接放到数组的最后一个位置,在将新数据放到前面。
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]));
        }
    }

memmove和memcpy

  • memmove和memcpy两个函数的作用都是进行内存拷贝,唯一的区别就是当要拷贝的内存区域和目标内存区域有重叠部分的时候,memmove能够保证拷贝之后的结果是正确的,但是memcpy就不能保证拷贝之后的结果是正确的。

memmove

memmove的函数声明如下

void    *memmove(void *__dst, const void *__src, size_t __len);
  • memmove() 函数从src内存中拷贝n个字节到dest内存区域,但是源和目的的内存可以重叠,具体拷贝流程如下:
    image
  • 可以看到,src和dest的区域存在重叠部分,并且dest的区域在src的后面,所有在执行memmove操作时,会从src的最后一个内存中的元素开始,依次往后挪动,所以挪动完成之后就完成了拷贝操作,而且结果是正确的。

memcpy

  • memcpy的函数声明如下
void    *memcpy(void *__dst, const void *__src, size_t __n);
  • memcpy()函数从src内存中拷贝n个字节到dest内存区域,但是源和目的的内存区域不能重叠,具体拷贝流程如下
    image

-可以看到src和dest的区域存在重叠部分,在执行memcpy操作时,会从src起始内存地址开始,依次向后copy,在图中即表示为将src中第一个元素拷贝到dest第一个地址中,将src第二个元素拷贝到dest的第二个地址中,所以就造成了覆盖操作。

你可能感兴趣的:(OC类的加载-上)