OC底层原理(十三):类与分类加载

7,783 阅读32分钟

dyld4流程下中,我们探索和分析了map_imagesload_images这两个函数;但是没有对类的加载做出详细解释,本文就探讨下类的加载。

一、类的加载

在前一篇文章中read_images函数中,我们说到非懒加载类的加载是通过调用realizeClassWithoutSwift来实现的,那我们就首先看下这个函数源码

截屏2023-02-21 下午5.11.30.png

  • 结论:

    realizeClassWithoutSwift函数的主要作用:

    • ①. 读取data数据,并设置rorw

    • ②. 递归调用realizeClassWithoutSwift完善继承链;

    • ③. 处理方法、属性、协议列表等。

读取data数据并设置rorw

通过llvm的方式来打印调试下是否真的存储了数据。首先我们准备一个类CJNonlazyClass,并实现load方法。

  • 代码:
@interface CJNonlazyClass : NSObject

@property (strong, nonatomic) NSString *name;
@property (assign, nonatomic) int age;

- (void)instanceMethod0;
- (void)instanceMethod1;

@end

@implementation CJNonlazyClass

+ (void)load {
    NSLog(@"%s", __func__);
}

@end
  1. 然后在realizeClassWithoutSwift函数体里边添加
  • 代码:
// fixme verify class is not in an un-dlopened part of the shared cache?
    // CJNonlazyClass为我自己添加的类名
    const char *nonlazyClassName = "CJNonlazyClass";
    auto cj_ro = (const class_ro_t *)cls->safe_ro();
    // 判断是否是元类
    auto cj_isMeta = cj_ro->flags & RO_META;
    if (strcmp(class_getName(cls), nonlazyClassName) == 0 && !cj_isMeta) {
        printf("你来了");
    }
  • 结论:

    这段代码主要是为了调试使用,方便跟踪我们自己写的类初始化ro、rw使用。

  1. 在判断条件中打上断点调试,运行后查看ro情况: 截屏2023-02-26 下午6.52.31.png

  2. 可以看到ro的数据,是从Mach-O读取到内存时,就已经存储在bits中,通过cls->data()就可以获取到,并且这里的ro是作为一个临时变量存在的。

(lldb) x/6gx cls
0x1000084e0: 0x00000001000084b8 0x0000000100721140
0x1000084f0: 0x0000000100719cc0 0x0000000000000000
0x100008500: 0x0000000100008158 0x0000000100008530
(lldb) p (class_data_bits_t *)0x100008500
(class_data_bits_t *) $1 = 0x0000000100008500
(lldb) p *$1
(class_data_bits_t) $2 = (bits = 4295000408)
(lldb) p $2.data()
(class_rw_t *) $3 = 0x0000000100008158
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 388
  witness = 8
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 24
    }
  }
  firstSubclass = 0x0000000100003e60
  nextSiblingClass = 0x0000000100003e51
}
(lldb) p $4.ro()
(const class_ro_t *) $5 = 0x0000000000000018
(lldb) p *$5
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
(lldb) 
  1. 在这里就可以看到,一开始cls调用ro()是没有数据的,也就是ro_or_rw_ext无值的,当继续运行:
(lldb) p $2.data()
(class_rw_t *) $7 = 0x00006000002341a0
(lldb) p *$7
(class_rw_t) $8 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000408
    }
  }
  firstSubclass = nil
  nextSiblingClass = nil
}
(lldb) p $8.ro()
(const class_ro_t *) $9 = 0x0000000100008158
(lldb) p *$9
(const class_ro_t) $10 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003e60 "\U00000011"
    nonMetaclass = 0x0000000100003e60
  }
  name = {
    std::__1::atomic<const char *> = "CJNonlazyClass" {
      Value = 0x0000000100003e51 "CJNonlazyClass"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008068
  }
  baseProtocols = nil
  ivars = 0x00000001000080e8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008130
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $10.ivars
(const ivar_list_t *const) $11 = 0x00000001000080e8
(lldb) p *$11
(const ivar_list_t) $12 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 2)
}
(lldb) p $12.get(0)
(ivar_t) $13 = {
  offset = 0x0000000100008490
  name = 0x0000000100003e9d "_age"
  type = 0x0000000100003f7f "i"
  alignment_raw = 2
  size = 4
}
(lldb) p $12.get(1)
(ivar_t) $14 = {
  offset = 0x0000000100008498
  name = 0x0000000100003ea2 "_name"
  type = 0x0000000100003f81 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  1. 代码走过rw->set_ro(ro)以及cls->setData(rw)之后,我们打印clsro(),此时就有了数据,
(lldb) p $10.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $15 = {
  ptr = 0x0000000100008068
}
(lldb) p $15.ptr
(method_list_t *const) $16 = 0x0000000100008068
(lldb) p *$16
(method_list_t) $17 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 5)
}
(lldb) p $17.get(0)
(method_t) $18 = {}
(lldb) p $17.get(0).getDescription()
(objc_method_description *) $19 = 0x0000000100008070
(lldb) p *$19
(objc_method_description) $20 = (name = "name", types = "@16@0:8")
(lldb) p *($17.get(1).getDescription())
(objc_method_description) $21 = (name = "setName:", types = "v24@0:8@16")
(lldb) p *($17.get(2).getDescription())
(objc_method_description) $22 = (name = "age", types = "i16@0:8")
(lldb) p *($17.get(3).getDescription())
(objc_method_description) $23 = (name = "setAge:", types = "v20@0:8i16")
(lldb) p *($17.get(4).getDescription())
(objc_method_description) $24 = (name = ".cxx_destruct", types = "v16@0:8")
(lldb) p *($17.get(5).getDescription())
Assertion failed: (i < count), function get, file objc-runtime-new.h, line 698.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
(lldb) 
  1. 在之前的文章OC类的探索-bits当中,
  • ①. 当类第一次从磁盘加载到内存时,会在bits中获取到ro

image.png

  • ②. 然后由ro来设置rw的数据,这里是通过rw->set_ro(ro)来设置rw中的ro值。 image.png

  • ③. 而ro的获取,会根据情况分别取值:

    • 如果有运行时,从rw中读取
    • 如果没有运行时,从ro中读取 也就是获取当时存储在ro_or_rw_ext中的ro,也就有了数据,
  const class_ro_t *ro() const {
       auto v = get_ro_or_rwe();
       if (slowpath(v.is<class_rw_ext_t *>())) {
           return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
       }
       return v.get<const class_ro_t *>(&ro_or_rw_ext);
   }

   void set_ro(const class_ro_t *ro) {
       auto v = get_ro_or_rwe();
       if (v.is<class_rw_ext_t *>()) {
           v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
       } else {
           set_ro_or_rwe(ro);
       }
   }

获取父类、元类等,完善继承链

然后通过递归调用realizeClassWithoutSwift来获取父类元类,在这里如果递归到之前实现的类(通过cls->isRealized()来判断),终止递归,并返回。

(lldb) x/6gx cls
0x1000084e0: 0x00000001000084b8 0x0000000100721140
0x1000084f0: 0x0000000100719cc0 0x0000000000000000
0x100008500: 0x80006000002341a0 0x0000000100008530
(lldb) po metacls
objc[11887]: mutex incorrectly locked
objc[11887]: mutex incorrectly locked
0x00000001000084b8

(lldb) po supercls
objc[11887]: mutex incorrectly locked
objc[11887]: mutex incorrectly locked
0x0000000100721140

(lldb) po class_getName(supercls)
"NSObject"
  1. 然后通过如下代码,对当前cls设置父类元类
// 将父类和元类赋值给当前类,分别是isa和父类的对应值 
// Update superclass and metaclass in case of remapping
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);
  1. 通过如下代码,来对父类中的子类列表添加子类
//双向链表指向关系,父类中可以找到子类,子类中也可以找到父类 
//通过addSubclass把当前类放到父类的子类列表中去 
// Connect this class to its superclass's subclass lists 
if (supercls) { 
    addSubclass(supercls, cls); 
} else { 
   addRootClass(cls); 
}

methodizeClass

realizeClassWithoutSwift函数的另一个功能就是处理方法、属性、协议列表,也就是methodizeClass函数,先看下源码

  • 总结:

    来看methodizeClass函数主要分如下部分:

    • ①. 获取ro中的baseMethods,并对方法列表进行排序

    • ②. 如果rwe存在,将属性列表、方法列表、协议列表等添加到rwe中,由于现阶段我们看的是非懒加载类的加载,所以这时候的rwe是没有值的(如果没有runtime的Api或者分类对方法进行改动,rwe都是没有值的),所以attachListsattachToClass这俩函数在下面中说明。

prepareMethodLists方法排序

根据断点调试,这里会调用一个prepareMethodLists方法,也就是方法排序的过程在之前objc_msgSend消息的慢速查找分析一文中,在进行方法的慢查找时,是通过二分查找来进行的,但二分查找的前提是方法有序的,方法的有序排序就是在此处实现的:

static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle, const char *why)
{
    lockdebug::assert_locked(&runtimeLock);

    if (addedCount == 0) return;
    
    // 某些类的基方法存在RR/AWZ/Core特例。
    // 但这段代码永远不需要扫描RR/AWZ/Core的基本方法:
    // 无法在setInitialized()之前设置默认RR/AWZ/Core。
    // 因此,我们不需要在这里处理任何特殊情况。
    if (baseMethods) {
        ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
    } else if (cls->cache.isConstantOptimizedCache()) {
        cls->setDisallowPreoptCachesRecursively(why);
    } else if (cls->allowsPreoptInlinedSels()) {
#if CONFIG_USE_PREOPT_CACHES
        SEL *sels = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_START];
        SEL *sels_end = (SEL *)objc_opt_offsets[OBJC_OPT_INLINED_METHODS_END];
        if (method_lists_contains_any(addedLists, addedLists + addedCount, sels, sels_end - sels)) {
            cls->setDisallowPreoptInlinedSelsRecursively(why);
        }
#endif
    }

    // 将方法列表添加到数组。
    // 重新分配un-fixed未固定的方法列表。
    // 新方法被置于方法列表数组之前。
    for (int i = 0; i < addedCount; i++) {
        method_list_t *mlist = addedLists[i];
        ASSERT(mlist);

        // Fixup selectors if necessary
        if (!mlist->isFixedUp()) {
            fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
        }
    }
    
    // 如果类已初始化,则扫描由类的标志跟踪的方法实现。
    // 如果它尚未初始化,那么objc_class::setInitialized()将处理它。
    if (cls->isInitialized()) {
        objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
        objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
    }
}
  • 总结:

    通过prepareMethodLists函数将ro->baseMethods传入,然后再调用fixupMethodList函数进行排序。

fixupMethodList

fixupMethodList这里我添加了个调试打印的方法,来查看排序前后的方法列表。

static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    lockdebug::assert_locked(&runtimeLock);
    ASSERT(!mlist->isFixedUp());

    // fixme lock less in attachMethodLists ?
    // dyld3 may have already uniqued, but not sorted, the list
    //重新处理方法的sel
    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            const char *name = sel_cname(meth.name());
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
            printf("排序前: %s - %p\n",name, meth.name()); //这里添加个调试方法,方便观察
        }
    }

    // 排序方法,如果是小端模式就不需要排序
    if (sort && mlist->listKind() != method_t::Kind::small && mlist->entsize() == method_t::bigSize)
        //这里就是排序方法
        mlist->sortBySELAddress();
   
    =============这里是为了方便方法sel打印插入的代码,非源码==================
    // Unique selectors in list.
    for (auto& meth : *mlist) {
        const char *name = sel_cname(meth.name());
        printf("排序后: %s - %p\n", name, meth.name());
    }
    printf("********************\n");
    ==================================================================
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (mlist->listKind() != method_t::Kind::small) {
        mlist->setFixedUp();
    }
}

  • 调试图: 截屏2023-02-27 上午12.25.25.png

  • llvm调试:

你来了排序前: load - 0x7ff82a75dda4
********************
排序后: load - 0x7ff82a75dda4
********************
排序前: name - 0x7ff82a75debf
排序前: setName: - 0x7ff82a7643d2
排序前: age - 0x7ff82ab65cfd
排序前: setAge: - 0x7ff82ab65d01
排序前: .cxx_destruct - 0x7ff82a760aeb
********************
排序后: name - 0x7ff82a75debf
排序后: .cxx_destruct - 0x7ff82a760aeb
排序后: setName: - 0x7ff82a7643d2
排序后: age - 0x7ff82ab65cfd
排序后: setAge: - 0x7ff82ab65d01
  • 结论:

    可以明显的看到排序前方法sel的地址是无序的,在排序后方法sel的地址是从小到大的顺序排序,函数的排序是根据sel的地址来进行排序的。

懒加载类与非懒加载类

懒加载类和非懒加载类的区别就是是否实现+load方法

  • ①. 非懒加载类:就是实现+load方法;

  • ②. 懒加载类:就是没实现,因为+load会提前加载,load类方法会在load_images中调用。

那么懒加载类非懒加载在加载的时候各是如何加载的呢?

由于类的加载方法是realizeClassWithoutSwift,所以懒加载类非懒加载类的主要区别就是调用这个方法的流程不同。

  • ①. 首先是非懒加载类的调用堆栈: map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 9.1
  * frame #0: 0x00000001006a2b70 libobjc.A.dylib`realizeClassWithoutSwift(cls=0x00000001000084e8, previously=0x0000000000000000) at objc-runtime-new.mm:2631:9
    frame #1: 0x00000001006a5135 libobjc.A.dylib`_read_images(hList=0x00007ff7bfefb550, hCount=56, totalClasses=2133, unoptimizedTotalClasses=2133) at objc-runtime-new.mm:3855:13
    frame #2: 0x00000001006f5108 libobjc.A.dylib`map_images_nolock(mhCount=56, mhPaths=0x00007ff7bfefc6e0, mhdrs=0x00007ff7bfefcbb0, disabledClassROEnforcement=0x00007ff7bfefb867) at objc-os.mm:470:9
    frame #3: 0x00000001006a3da7 libobjc.A.dylib`map_images(count=56, paths=0x00007ff7bfefc6e0, mhdrs=0x00007ff7bfefcbb0) at objc-runtime-new.mm:3170:9
    frame #4: 0x00007ff80a3fb4c3 dyld`invocation function for block in dyld4::RuntimeState::setObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*), void (*)(mach_header const*, void*, mach_header const*, void const*), void (*)(unsigned int, _dyld_objc_notify_mapped_info const*)) + 637
    frame #5: 0x00007ff80a3f5fff dyld`dyld4::RuntimeState::withLoadersReadLock(void () block_pointer) + 47
    frame #6: 0x00007ff80a3fb240 dyld`dyld4::RuntimeState::setObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*), void (*)(mach_header const*, void*, mach_header const*, void const*), void (*)(unsigned int, _dyld_objc_notify_mapped_info const*)) + 96
    frame #7: 0x00007ff80a41f5e4 dyld`dyld4::APIs::_dyld_objc_register_callbacks(_dyld_objc_callbacks const*) + 138
    frame #8: 0x00000001006f62cd libobjc.A.dylib`_objc_init at objc-os.mm:815:5
    frame #9: 0x00000001000f575d libdispatch.dylib`_os_object_init + 13
    frame #10: 0x0000000100107396 libdispatch.dylib`libdispatch_init + 363
    frame #11: 0x00007ff816348895 libSystem.B.dylib`libSystem_initializer + 238
    frame #12: 0x00007ff80a405618 dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 172
    frame #13: 0x00007ff80a444de9 dyld`invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 242
    frame #14: 0x00007ff80a438ef7 dyld`invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 557
    frame #15: 0x00007ff80a3eb0b7 dyld`dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const + 245
    frame #16: 0x00007ff80a4380a7 dyld`dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 175
    frame #17: 0x00007ff80a4448d2 dyld`dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 470
    frame #18: 0x00007ff80a4054f6 dyld`dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 150
    frame #19: 0x00007ff80a42500d dyld`dyld4::APIs::runAllInitializersForMain() + 71
    frame #20: 0x00007ff80a3f0369 dyld`dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3743
    frame #21: 0x00007ff80a3ef281 dyld`start + 2289
(lldb)
  • ②. 懒加载类的调用堆栈:lookUpImpOrForward -> realizeAndInitializeIfNeeded_locked -> initializeAndLeaveLocked -> initializeAndMaybeRelock -> realizeClassMaybeSwiftAndUnlock -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 9.1
  * frame #0: 0x00000001006a2b70 libobjc.A.dylib`realizeClassWithoutSwift(cls=0x00000001000084c8, previously=0x0000000000000000) at objc-runtime-new.mm:2631:9
    frame #1: 0x00000001006cfb7e libobjc.A.dylib`realizeClassMaybeSwiftMaybeRelock(cls=0x00000001000084c8, lock=0x00000001007240c0, leaveLocked=false) at objc-runtime-new.mm:2897:9
    frame #2: 0x00000001006b7dbf libobjc.A.dylib`realizeClassMaybeSwiftAndUnlock(cls=0x00000001000084c8, lock=0x00000001007240c0) at objc-runtime-new.mm:2914:12
    frame #3: 0x00000001006a2418 libobjc.A.dylib`initializeAndMaybeRelock(cls=0x00000001000084a0, inst=0x00000001000084c8, lock=0x00000001007240c0, leaveLocked=true) at objc-runtime-new.mm:2214:19
    frame #4: 0x00000001006d0c6a libobjc.A.dylib`initializeAndLeaveLocked(cls=0x00000001000084a0, obj=0x00000001000084c8, lock=0x00000001007240c0) at objc-runtime-new.mm:2239:12
    frame #5: 0x00000001006b31c0 libobjc.A.dylib`realizeAndInitializeIfNeeded_locked(inst=0x00000001000084c8, cls=0x00000001000084a0, initialize=true) at objc-runtime-new.mm:6772:15
    frame #6: 0x00000001006b2c7c libobjc.A.dylib`lookUpImpOrForward(inst=0x00000001000084c8, sel="alloc", cls=0x00000001000084a0, behavior=11) at objc-runtime-new.mm:6882:11
    frame #7: 0x00000001006edc5b libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1153
    frame #8: 0x00000001007047f4 libobjc.A.dylib`objc_alloc [inlined] callAlloc(cls=0x00000001000084c8, checkNil=true, allocWithZone=false) at NSObject.mm:2011:12
    frame #9: 0x000000010070474c libobjc.A.dylib`objc_alloc(cls=0x00000001000084c8) at NSObject.mm:2027:12
    frame #10: 0x00000001000039db KCObjcBuild`main(argc=1, argv=0x00007ff7bfeff538) at main.m:212:33 [opt]
    frame #11: 0x00007ff80a3ef310 dyld`start + 2432
(lldb) 
  • 总结:

    不管是懒加载类或者是非懒加载类,最终都会调用realizeClassWithoutSwift来进行实现类,但是它们还是有区别:

    • ①. 懒加载类就是没有实现+load方法的类,会在其使用的时候(即第一次进行消息慢速查找时)才会去加载。

    • ②. 非懒加载类就是实现了+load方法的类,会在objc_initmap_images时去加载。

二、分类

平时在开发的时候会经常使用分类来添加方法、协议、属性,但在添加属性的时候属性是不会自动生成成员变量的,这时候我们就需要关联对象来动态存储属性值

分类的本质

  1. 首先要知道分类的本质是什么,我们定义一个分类
  • objectiveC源码:
// 定义一个类
@interface CJPerson : NSObject
@property (nonatomic, copy) NSString *hobby;
- (void)printClassAllMethod: (Class) cls;
- (void)test;
@end

@implementation CJPerson
+ (void)load {
    NSLog(@"%s", __func__);
}

- (void)test {
    NSLog(@"%s", __func__);
}

- (void)printClassAllMethod:(Class)cls {
    NSLog(@"%s", __func__);
}
@end
//----------------------------------------------
// 分类内容
@interface CJPerson (Category)
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)test;
- (void)category_instanceMethod;
+ (void)category_classMethod;
@end

@implementation CJPerson (Category)
+ (void)load {
    NSLog(@"%s", __func__);
}

- (void)test {
    NSLog(@"%s", __func__);
}

- (void)category_instanceMethod {
    NSLog(@"%s", __func__);
}

+ (void)category_classMethod {
    NSLog(@"%s", __func__);
}
@end

  1. 在终端里使用clang -rewrite-objc CJPerson+Category.m -o CJPerson+Category.cpp重写将上面的OC代码成c++代码。
  • 图: 截屏2023-02-27 下午11.32.50.png

  • c++源码里分类结构:

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;
};
  1. 可以看到其根本的实现是_category_t这个结构,那么我们可以借助objc4(866.9)源码来查找关于category_t的定义。
  • objc4源码里分类结构:
struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
    WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);

    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
}
  1. 那么CJPerson+Category这个分类,其底层存储了哪些内容呢?我们可以在c++里通过搜索_OBJC_$_CATEGORY_CJPerson_找到其底层实现:
static struct _category_t _OBJC_$_CATEGORY_CJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"CJPerson",
	0, // &OBJC_CLASS_$_CJPerson,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CJPerson_$_Category,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CJPerson_$_Category,
	0,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CJPerson_$_Category,
};
  1. 看到该结构体对应category_t结构体六个值,但是第二个值cls的值为0,这是为什么呢?
    这是因为在编译阶段CJPerson+Category这个分类还没有与CJPerson进行关联。因此cls0
  • ①. instance_methods实例方法列表对应了_OBJC_$_CATEGORY_INSTANCE_METHODS_CJPerson_$_Category
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_CJPerson_Category_test},
	{(struct objc_selector *)"category_instanceMethod", "v16@0:8", (void *)_I_CJPerson_Category_category_instanceMethod}}
};

  • 总结:

    可以看到这里就两个实例方法testcategory_instanceMethod,格式为:sel+签名+地址,类型为method_t,这两个method_t组成method_list

  • ②. class_methods类方法列表对应了_OBJC_$_CATEGORY_CLASS_METHODS_CJPerson_$_Category

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_CLASS_METHODS_CJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"load", "v16@0:8", (void *)_C_CJPerson_Category_load},
	{(struct objc_selector *)"category_classMethod", "v16@0:8", (void *)_C_CJPerson_Category_category_classMethod}}
};
  • 总结:

    class_methods这里就是类方法列表,包含编译后的loadcategoty_classMethod

  • ③. properties属性列表对应_OBJC_$_PROP_LIST_CJPerson_$_Category

static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[2];
} _OBJC_$_PROP_LIST_CJPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	2,
	{{"name","T@\"NSString\",C,N"},
	{"age","Tq,N"}}
};
  • 总结:

    属性列表中存储了分类中的属性,但是分类并没有对应的成员变量,而且也没有set/get方法。

总结:

  • 分类的本质是一个category_t类型的结构体:

    • ①. 结构体内有两个属性 name(类的名称)cls(类对象)

    • ②. 有两个method_list_t类型的的方法列表,分别表示分类中实现的实例方法类方法

    • ③. _protocol_list_t类型的是协议列表,表示分类实现的协议

    • ④. _prop_list_t类型的属性列表,只表示分类中定义的属性(只有定义)

  • 分类中没有成员变量,也没有setget方法,分类中的属性都是通过关联对象来实现的。

分类的加载

其实分类主要加载方法就是attachCategories,通过在源码搜索这个函数,可以发现这个方法的调用路径有三种

  • map_images -> map_images_nolock -> _read_images -> load_categories_nolock -> attachCategories

  • load_images -> loadAllCategories-> load_categories_nolock -> attachCategories

  • realizeClassWithoutSwift-> methodizeClass -> objc::unattachedCategories.attachToClass -> attachCategories

  • 总结:

    • ①. 第一条路径:根据didInitialAttachCategories判断条件去判断是否加载分类?可是didInitialAttachCategoriesload_images后才会被赋值为true,而这个流程是在map_images中的,dyld4是先map_imagesload_images的,所以这个过程可以忽略不计。

    • ②. 第二条路径:主要是在非懒加载类情况下,单个或多个非懒加载分类是一个接一个从Mach-O里读取加载依附到rwe

    • ③.第三条路径:主要是在懒加载类情况下,多个非懒加载分类多个一下子加载依附到类的rwe

loadAllCategories

static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);

    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        load_categories_nolock(hi);
    }
}
  • 结论:

    loadAllCategories就是读取Mach-O里的分类数据,分步使用load_categories_nolock加载分类数据。

load_categories_nolock

static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    // processCatlist 是函数的实现 这里可以看作是一个闭包
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};

            if (!cls) {
                // 分类的目标类缺失(可能是弱链接的)。
                // 忽略分类。
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // 处理此分类。
            if (cls->isStubClass()) {
                // 无法确定元类对象是哪个 所以先附着在stubClass身上
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // 首先,向其目标类注册分类。
                // 然后,如果实现了该类,则重建该类的方法列表(等)。
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    if (cls->isRealized()) {
                        //如果类已经实现或者类已经加载了
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        //如果类没有实现
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    if (cls->ISA()->isRealized()) {
                        // *****如果类的元类已经实现或者元类已经被加载了
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        // 如果元类没有实现
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    // 调用processCatlist
    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

  • 总结:
    • ①. cls为分类要关联的本类,通过remapClass(cat->cls)函数来获取,在readclass函数中,会将读取的类,通过addRemappedClass函数来添加到DenseMap表中,而此处是获取存储在表中的类来做关联。

    • ②. 如果cls类之前没有加载,就调用addForClass函数,将分类数据添加到哈希表中去(addForClass中的get()函数就是获取到这张哈希表,在attachToClass函数那里,也是会通过get()函数再把这个表中的值取出来)。

    • ③. 读取出来的cls,如果这个类是个非懒加载类,也就是这个类已经加载过(已经设置过rw)那么调用attachCategories,添加分类数据。

    • ④. attachCategories通过clsflags参数区分类和元类。cats_count参数写死的是1locstamped_category_t是由lc{cat, hi}分类和header_info组成。

attachToClass

首先attachToClass并不是在load_categories_nolock函数中调用的,他的调用堆栈为realizeClassWithoutSwift -> methodizeClass -> attachToClass,这里说一下这个函数是因为addForClass函数,在load_categories_nolock函数中会把非懒加载分类数据添加到表中去,而attachToClass函数是把表中的分类数据添加到类中的rwe中去。

class UnattachedCategories : public ExplicitInitDenseMap<Class, category_list>
{
public:
    ...
    ...
  void attachToClass(Class cls, Class previously, int flags)
    {
        lockdebug::assert_locked(&runtimeLock);
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);

        if (it != map.end()) {
            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

// 将方法列表、属性和协议从分类附加到类。
// 假设cats中的分类都是按加载顺序加载和排序的,首先是最旧的分类。
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)" : "");
    }

    /*
     * 只有少数类在发布期间有超过64个分类。
     * 这使用了一个小堆栈,避免了malloc。
     *
     * 分类必须以正确的顺序添加,即从后到前。
     * 为了实现这一点,我们从前面到后面迭代cats_list,
     * 向后构建本地缓冲区,并在块上调用attachList。
     * attachList在列表前面,所以最终结果是按照预期的顺序。 
     *
     */
    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();
    //debug代码可以放这里
    //遍历每个分类
    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, __func__);
                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, __func__);
        //添加分类的方法到rwe中
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
  • 总结:

    • ①. attachCategories函数首先通过extAllocIfNeeded创建了rwe数据。

    • ②. 在非懒加载类调用流程中,也就是通过load_categories_nolock函数调用attachCategories函数,cats_count传值为1,所以这里相当于没有循环。通过methodsForMeta获取分类方法列表。

    • ③. ATTACH_BUFSIZ的值为64,当mcount64的时候,重新开始计数。也就是说当cats_count > 64的时候会重新进行计数。但是目前loadAllCategories传递的是1所以不会进入这里的逻辑。那么只有attachToClass会进入这个逻辑了。

    • ④. 之后调用prepareMethodLists进行排序,然后会调用attachLists将分类数据加入rwe中。

extAllocIfNeeded

 class_rw_ext_t *extAllocIfNeeded() {
        // 获取rwe
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            // 如果之前创建了直接返回
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            // 创建rwe
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    lockdebug::assert_locked(&runtimeLock);
    // 调用alloc创建空间
    auto rwe = objc::zalloc<class_rw_ext_t>();
    // 设置版本,元类为7,非元类为0。
    rwe->version = (ro->flags & RO_META) ? 7 : 0;
    // 获取ro中的方法列表
    method_list_t *list = ro->baseMethods;
    if (list) {
        // 是否深拷贝,跟踪的流程中 deepCopy 为false
        if (deepCopy) list = list->duplicate();
        // 将ro的方法列表放入rwe中。
        rwe->methods.attachLists(&list, 1);
    }

    // 查看objc_duplicateClass属性列表和协议列表中的注释,
    // 这些列表历史上未被深度复制
    // 这可能是错误的,应该改天解决
    // 属性
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }
    // 协议
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }
    // 设置rwe,rwe-ro = ro
    set_ro_or_rwe(rwe, ro);
    return rwe;
}

  • 总结:

    • ①. extAllocIfNeeded函数内部调用是extAlloc创建rwe,如果之前创建过直接返回
    • ②. 将romethods数据拷贝到rwe中(这里没有深拷贝,通过extAllocIfNeeded函数调用的都是浅拷贝)。
    • ③. 链接属性协议
    • ④. 设置rwerwe->ro = ro也就是rwe中的ro指向ro

methodsForMeta

 method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
  • 结论:

    判断是否元类,元类返回classMethods,类返回instanceMethods,对应category_t结构体中的类方法列表实例方法列表

attachLists

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

        if (hasArray()) {
            // many lists -> many lists
            // 计算旧的list大小
            uint32_t oldCount = array()->count;
            // 计算新的容量大小 = 旧数据大小+新数据大小
            uint32_t newCount = oldCount + addedCount;
            // 创建新的数组开辟空间
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            // 设置数组大小
            newArray->count = newCount;
            array()->count = newCount;
            // 旧的数据从addedCount下标开始,存放旧的数据
            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            // 新的数据从0开始存放
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            // 释放旧数据
            free(array());
            // 设置新数据新数组
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 当本类没有方法时走这里
            // 0 lists -> 1 list
            // 一维数组
            list = addedLists[0];
            validate();
        }
        else {
            // 当本类有方法时
            // 1 list -> many lists
            Ptr<List> oldList = list;
            // 有旧列表,oldCount为1否则为0
            uint32_t oldCount = oldList ? 1 : 0;
            // 新的count为旧的count+添加的count,也就是计算新的容量和
            uint32_t newCount = oldCount + addedCount;
            // 开辟新空间,设置新数组,类型为array_t
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            // 设置数组大小
            array()->count = newCount;
            // 判断旧的数组oldList是否存在,到这里oldList是肯定存在,将旧的数组oldList放到新数组后面
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++)
                    // 新的数组从0开始添加
                array()->lists[i] = addedLists[i];
            validate();
        }
    }
    ...
}
  • 总结:

    attachLists方法主要是将分类的数据进行合并

    • ①. 首先加载本类的数据,如果此时本类数据为空,也就是list为空,那么分类数据addedLists就是list,也就是0对1的流程

    • ②. 如果本类中有数据或者list有数据(加载过一个分类),那么就是1对多的流程

    • ③. 如果之前已经有很多list数据(加载过多个分类),那么就是多对多的流程。 截屏2023-03-01 下午1.34.04.png

三、探究类与分类加载的流程

通过对类的加载分类的加载源码分析,我们可以根据是否懒加载的情况?大致将类与分类的加载分为以下八种情况:

类+分类单个分类多个分类单个分类+实现load多个分类+实现load
懒加载类+懒加载分类懒加载类+多个懒加载分类懒加载类+非懒加载分类懒加载类+多个非懒加载分类
类+实现load非懒加载类+懒加载分类非懒加载类+多个懒加载分类非懒加载类+非懒加载分类非懒加载类+多个非懒加载分类
  • 准备工作:
  1. objc4-886.9realizeClassWithoutSwift函数中添加调试代码并打上断点,方便在自定义的主类加载过程暂停:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    ...
    ...
    const char *mangledNamecus = cls->nonlazyMangledName();
    const char *personName = "CJPerson"; //自定义的类名
    auto lg_ro = (const class_ro_t *)cls->data();
    auto lg_isMeta = lg_ro->flags & RO_META;
    if (strcmp(mangledNamecus, personName) == 0 && !lg_isMeta) {
         printf("%s",__func__);
    }
    ...
    ...
}
  1. 分类加载函数attachCategories里添加同样调试代码,用于调试捕获流程。

attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    ...
    ...
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();
    // 调试代码
    const char *mangledNamecus = cls->nonlazyMangledName();
    const char *personName = "CJPerson";
    auto lg_ro = (const class_ro_t *)cls->data();
    auto lg_isMeta = lg_ro->flags & RO_META;
    if (strcmp(mangledNamecus, personName) == 0 && !lg_isMeta) {
         printf("%s",__func__);
    }
    ...
    ...
}
  1. 自定义的类CJPerson与多个分类CJPerson+CACJPerson+CBCJPerson+CC。主类的instanceMethods实例方法有7个,CJPerson+CACJPerson+CB3个,CJPerson+CC4个。
  • 主类源码:
// 主类CJPerson
@interface CJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)say;
+ (void)class_method;
- (void)test;
@end

@implementation CJPerson
// 是否复写这个类方法,确定是否为懒加载主类
+ (void)load {
    NSLog(@"%s", __func__);
}

- (void)test {
    NSLog(@"%s", __func__);
}

- (void)say {
    NSLog(@"%s", __func__);
}

+ (void)class_method {
    NSLog(@"%s", __func__);
}

@end
  • 分类CJPerson+CA源码:
@interface CJPerson (CA)
@property (nonatomic, copy) NSString *ca_name;
@property (nonatomic, assign) NSInteger ca_age;

- (void)ca_say;
- (void)ca_eat;

+ (void)ca_class_method;
@end

@implementation CJPerson (CA)
// 是否复写类方法load,决定是否为懒加载分类
+ (void)load {
    NSLog(@"%s", __func__);
}
- (void)ca_say {
    NSLog(@"%s", __func__);
}

- (void)ca_eat {
    NSLog(@"%s", __func__);
}

+ (void)ca_class_method {
    NSLog(@"%s", __func__);
}

// 实现主类声明的实例方法
- (void)test {
    NSLog(@"%s", __func__);
}
@end
  • 分类CJPerson+CB源码:
@interface CJPerson (CB)

- (void)cb_say;
- (void)cb_eat;

+ (void)cb_class_method;

@end
@implementation CJPerson (CB)
// 是否复写类方法load,决定是否为懒加载分类
+ (void)load {
    NSLog(@"%s", __func__);
}

- (void)cb_say {
    NSLog(@"%s", __func__);
}

- (void)cb_eat {
    NSLog(@"%s", __func__);
}

+ (void)cb_class_method {
    NSLog(@"%s", __func__);
}

- (void)test {
    NSLog(@"%s", __func__);
}
@end

  • 分类CJPerson+CC源码:
@interface CJPerson (CC)

- (void)cc_say;
- (void)cc_eat;
+ (void)cc_class_method;

@end

@implementation CJPerson (CC)
// 是否复写类方法load,决定是否为懒加载分类
+ (void)load {
    NSLog(@"%s", __func__);
}

- (void)cc_say {
    NSLog(@"%s", __func__);
}

- (void)cc_eat {
    NSLog(@"%s", __func__);
}

+ (void)cc_class_method {
    NSLog(@"%s", __func__);
}

- (void)say {
    NSLog(@"%s", __func__);
}

- (void)test {
    NSLog(@"%s", __func__);
}

@end

  • main源码:
#import <Foundation/Foundation.h>
#import <objc/message.h>
#import "CJPerson.h"
#import "CJPerson+CA.h"
// 通过屏蔽下两个分类与在Xcode的Compile Source里添加删除与更改编译顺序
// 来实现单个或者多个分类加载
#import "CJPerson+CB.h"
#import "CJPerson+CC.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 保证在全部主类与分类都是懒加载时,能在消息发送时加载类
        CJPerson * objc = [CJPerson alloc];
        [objc test];
        [objc say];
    }
    return 0;
}

懒加载主类以及懒加载分类

单个懒加载分类

main函数里只保留CJPerson+CA.h的引入,屏蔽其他两个分类。

  1. XcodeCompile Source里设置。
  • 图: 截屏2023-03-03 下午2.00.23.png
  1. 将主类CJPerson与分类CJPerson+CA里的类方法+load全部屏蔽起来。
  • 图: 截屏2023-03-03 下午2.06.53.png
  1. 运行可调试源码,会卡在CJPerson第一次进行消息慢速查找时调用主类加载函数realizeClassWithoutSwift
  • 图: 截屏2023-03-03 下午2.15.39.png
  1. 编译运行程序,程序卡在了断点,此时CJPerson类的加载方式为懒加载,打印CJPerson类中所有的方法。
  • llvm打印:
(lldb) x/6gx cls
0x1000082a0: 0x0000000100008278 0x0000000100721140
0x1000082b0: 0x0000000100719cb0 0x0024000000000000
0x1000082c0: 0x8000600000231a60 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000082c0
(class_data_bits_t *) $1 = 0x00000001000082c0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000600000231a60
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000280
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007ff856e78200
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x00000001000080d8
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f62 "\U00000001"
    nonMetaclass = 0x0000000100003f62
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f59 "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008180
  }
  baseProtocols = nil
  ivars = 0x0000000100008090
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $6 = {
  ptr = 0x0000000100008180
}
(lldb) p *$6.ptr
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 10)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "ca_say"
  types = 0x0000000100003f67 "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "ca_eat"
  types = 0x0000000100003f67 "v16@0:8"
  imp = 0x0000000100003d70 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "test"
  types = 0x0000000100003f67 "v16@0:8"
  imp = 0x0000000100003da0 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "test"
  types = 0x0000000100003f67 "v16@0:8"
  imp = 0x0000000100003bf0 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "say"
  types = 0x0000000100003f67 "v16@0:8"
  imp = 0x0000000100003c20 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
  name = "name"
  types = 0x0000000100003f6f "@16@0:8"
  imp = 0x0000000100003c50 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $7.get(6).big()
(method_t::big) $14 = {
  name = "setName:"
  types = 0x0000000100003f77 "v24@0:8@16"
  imp = 0x0000000100003c70 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $7.get(7).big()
(method_t::big) $15 = {
  name = "age"
  types = 0x0000000100003f82 "q16@0:8"
  imp = 0x0000000100003ca0 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $7.get(8).big()
(method_t::big) $16 = {
  name = "setAge:"
  types = 0x0000000100003f8a "v24@0:8q16"
  imp = 0x0000000100003cc0 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $7.get(9).big()
(method_t::big) $17 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f67 "v16@0:8"
  imp = 0x0000000100003ce0 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $3.ext()
(class_rw_ext_t *) $18 = nil
  • 结论:

    可以看到,是在未调用attachCategories函数之前,分类中的方法已经被加载到主类的方法列表中了,并且是加载到了robaseMethodList中,类的rw_extnil值,清空输出,过掉断点,程序并不会执行attachCategories函数。


总结:

  • ①. 当主类及其分类都为懒加载模式时,并不会调用attachCategories函数将分类中的方法附着到主类中,而是编译器完成了这部分操作,并且主类的方法列表一维的,顺序是分类中的方法在主类方法前。

  • ②. 分类数据加载后主类方法类别内存结构示意图如下所示: 截屏2023-03-05 下午1.48.59.png

多个懒加载分类

如果主类的分类很多,懒加载主类与多个懒加载分类又是如何处理的呢?

  1. XcodeCompile Source里设置。
  • 如图: 截屏2023-03-03 下午2.38.59.png
  1. 将其他两个分类CJPerson+CBCJPerson+CC里的类方法+load全部屏蔽起来。
  • 图: 截屏2023-03-03 下午2.47.40.png
  1. 运行可调试源码,跟单个懒加载分类一样都会停在CJPerson第一次进行消息慢速查找时调用主类加载函数realizeClassWithoutSwift
  • 调试图: 截屏2023-03-03 下午2.15.39.png
  1. 编译运行程序,会发现依然执行的是主类的懒加载流程,打印主类中ro方法列表中的methot_t信息
  • 打印信息:
(lldb) x/6gx cls
0x100008378: 0x0000000100008350 0x0000000100721140
0x100008388: 0x0000000100719cb0 0x0024000000000000
0x100008398: 0x8000600000236540 0x0000000000000000
(lldb) p (class_data_bits_t *)0x100008398
(class_data_bits_t *) $1 = 0x0000000100008398
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000600000236540
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000280
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007ff856e78200
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x00000001000080d8
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f68 "\U00000001"
    nonMetaclass = 0x0000000100003f68
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f5f "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x00000001000081b0
  }
  baseProtocols = nil
  ivars = 0x0000000100008090
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $6 = {
  ptr = 0x00000001000081b0
}
(lldb) p *$6.ptr
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 17)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "ca_say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003c30 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "ca_eat"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003c60 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003c90 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "cb_say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003b70 (KCObjcBuild`-[CJPerson(CB) cb_say])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "cb_eat"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003ba0 (KCObjcBuild`-[CJPerson(CB) cb_eat])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003bd0 (KCObjcBuild`-[CJPerson(CB) test])
}
(lldb) p $7.get(6).big()
(method_t::big) $14 = {
  name = "cc_say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003a80 (KCObjcBuild`-[CJPerson(CC) cc_say])
}
(lldb) p $7.get(7).big()
(method_t::big) $15 = {
  name = "cc_eat"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003ab0 (KCObjcBuild`-[CJPerson(CC) cc_eat])
}
(lldb) p $7.get(8).big()
(method_t::big) $16 = {
  name = "say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003ae0 (KCObjcBuild`-[CJPerson(CC) say])
}
(lldb) p $7.get(9).big()
(method_t::big) $17 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003b10 (KCObjcBuild`-[CJPerson(CC) test])
}
(lldb) p $7.get(10).big()
(method_t::big) $18 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003930 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $7.get(11).big()
(method_t::big) $19 = {
  name = "say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003960 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $7.get(12).big()
(method_t::big) $20 = {
  name = "name"
  types = 0x0000000100003f7b "@16@0:8"
  imp = 0x0000000100003990 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $7.get(13).big()
(method_t::big) $21 = {
  name = "setName:"
  types = 0x0000000100003f83 "v24@0:8@16"
  imp = 0x00000001000039b0 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $7.get(14).big()
(method_t::big) $22 = {
  name = "age"
  types = 0x0000000100003f8e "q16@0:8"
  imp = 0x00000001000039e0 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $7.get(15).big()
(method_t::big) $23 = {
  name = "setAge:"
  types = 0x0000000100003f96 "v24@0:8q16"
  imp = 0x0000000100003a00 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $7.get(16).big()
(method_t::big) $24 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003a20 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $3.ext()
(class_rw_ext_t *) $25 = nil
(lldb) 

总结:

  • ①. 当主类都为懒加载模式时,无论懒加载分类有多少个都是第一次进行消息慢速查找时,是编译器完成了分类中的方法附着到主类中,并且主类的方法列表一维的,顺序是分类中的方法在主类方法前。

  • ②. 可以发现,主类方法列表中的方法顺序是与分类的编译顺序有关的,每编译一个分类时,会将其所有的方法插入到其主类方法列表的最前面。示意图如下所示: 截屏2023-03-05 下午1.54.12.png

非懒加载主类以及懒加载分类

单个懒加载分类

主类中开启+load类方法,只留一个CJPerson分类CA

  1. 首先在XcodeCompile Source里设置成单个分类的模式,这个跟上面一样。
  • 设置图: 截屏2023-03-03 下午2.00.23.png
  1. 将主类CJPerson设置成非懒加载类
  • 图: 截屏2023-03-03 下午3.10.37.png
  1. 编译运行代码,程序执行到断点,查看一下函数调用栈。
  • 如图所示: 截屏2023-03-03 下午3.14.41.png

  • 结论:

    此时非懒加载类的路径为: map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift

4. 打印输出主类中方法列表信息。

  • 打印信息:
(lldb) x/6gx cls
0x1000082b8: 0x0000000100008290 0x0000000100721140
0x1000082c8: 0x0000000100719cb0 0x0024000000000000
0x1000082d8: 0x800060000023c000 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000082d8
(class_data_bits_t *) $1 = 0x00000001000082d8
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000060000023c000
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000280
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100721168
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x00000001000080d8
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f68 "\U00000001"
    nonMetaclass = 0x0000000100003f68
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f5f "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008198
  }
  baseProtocols = nil
  ivars = 0x0000000100008090
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $6 = {
  ptr = 0x0000000100008198
}
(lldb) p $6.ptr
(method_list_t *const) $7 = 0x0000000100008198
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 10)
}
(lldb) p $8.get(0).big()
(method_t::big) $9 = {
  name = "ca_say"
  types = 0x0000000100003f6d "v16@0:8"
  imp = 0x0000000100003d30 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $8.get(1).big()
(method_t::big) $10 = {
  name = "ca_eat"
  types = 0x0000000100003f6d "v16@0:8"
  imp = 0x0000000100003d60 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $8.get(2).big()
(method_t::big) $11 = {
  name = "test"
  types = 0x0000000100003f6d "v16@0:8"
  imp = 0x0000000100003d90 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p $8.get(3).big()
(method_t::big) $12 = {
  name = "test"
  types = 0x0000000100003f6d "v16@0:8"
  imp = 0x0000000100003be0 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $8.get(4).big()
(method_t::big) $13 = {
  name = "say"
  types = 0x0000000100003f6d "v16@0:8"
  imp = 0x0000000100003c10 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $8.get(5).big()
(method_t::big) $14 = {
  name = "name"
  types = 0x0000000100003f75 "@16@0:8"
  imp = 0x0000000100003c40 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $8.get(6).big()
(method_t::big) $15 = {
  name = "setName:"
  types = 0x0000000100003f7d "v24@0:8@16"
  imp = 0x0000000100003c60 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $8.get(7).big()
(method_t::big) $16 = {
  name = "age"
  types = 0x0000000100003f88 "q16@0:8"
  imp = 0x0000000100003c90 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $8.get(8).big()
(method_t::big) $17 = {
  name = "setAge:"
  types = 0x0000000100003f90 "v24@0:8q16"
  imp = 0x0000000100003cb0 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $8.get(9).big()
(method_t::big) $18 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f6d "v16@0:8"
  imp = 0x0000000100003cd0 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $3.ext()
(class_rw_ext_t *) $19 = nil
(lldb) 

总结:

  • ①.继续执行程序,发现程序依旧不会调用执行attachCategories函数,这种懒加载分类的加载方式跟上面的一样都是编译器完成。

  • ②. 非懒加载类与单个懒加载分类会在objc初始化的map_image映射镜像时,就开始加载主类,由于分类是懒加载的,编译器会自动将其放置到ro里,这个主要是编译器完成的工作,在生成合并mach-O文件时的优化工作。 截屏2023-03-05 下午2.01.04.png

多个懒加载分类

  1. 重新把剩余的两个分类加回XcodeComplie Source里,为了验证之前说尾插入分类而更改它们顺序下,按照CCCACB的先后编译顺序。
  • 如下图所示: 截屏2023-03-03 下午3.36.21 1.png
  1. 主类非懒加载分类CCCACB都是懒加载 截屏2023-03-03 下午3.42.46.png
  2. 编译运行程序,程序执行到断点,查看函数调用栈。
  • 如下图所示: 截屏2023-03-03 下午3.14.41.png

  • 结论:

    非懒加载类的路径为: map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift

  1. 并打印输出CJPerson类中方法列表的方法顺序
(lldb) x/6gx cls
0x100008390: 0x0000000100008368 0x0000000100721140
0x1000083a0: 0x0000000100719cb0 0x0024000000000000
0x1000083b0: 0x8000600000230700 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000083b0
(class_data_bits_t *) $1 = 0x00000001000083b0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000600000230700
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000280
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100721168
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x00000001000080d8
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f5e "\U00000001"
    nonMetaclass = 0x0000000100003f5e
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f55 "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x00000001000081c8
  }
  baseProtocols = nil
  ivars = 0x0000000100008090
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $6 = {
  ptr = 0x00000001000081c8
}
(lldb) p $6.ptr
(method_list_t *const) $7 = 0x00000001000081c8
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 17)
}
(lldb) p $8.get(0).big()
(method_t::big) $9 = {
  name = "cb_say"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003c10 (KCObjcBuild`-[CJPerson(CB) cb_say])
}
(lldb) p $8.get(1).big()
(method_t::big) $10 = {
  name = "cb_eat"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003c40 (KCObjcBuild`-[CJPerson(CB) cb_eat])
}
(lldb) p $8.get(2).big()
(method_t::big) $11 = {
  name = "test"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003c70 (KCObjcBuild`-[CJPerson(CB) test])
}
(lldb) p $8.get(3).big()
(method_t::big) $12 = {
  name = "ca_say"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003b50 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $8.get(4).big()
(method_t::big) $13 = {
  name = "ca_eat"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003b80 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $8.get(5).big()
(method_t::big) $14 = {
  name = "test"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003bb0 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p $8.get(6).big()
(method_t::big) $15 = {
  name = "cc_say"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003a60 (KCObjcBuild`-[CJPerson(CC) cc_say])
}
(lldb) p $8.get(7).big()
(method_t::big) $16 = {
  name = "cc_eat"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003a90 (KCObjcBuild`-[CJPerson(CC) cc_eat])
}
(lldb) p $8.get(8).big()
(method_t::big) $17 = {
  name = "say"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003ac0 (KCObjcBuild`-[CJPerson(CC) say])
}
(lldb) p $8.get(9).big()
(method_t::big) $18 = {
  name = "test"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003af0 (KCObjcBuild`-[CJPerson(CC) test])
}
(lldb) p $8.get(10).big()
(method_t::big) $19 = {
  name = "test"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003910 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $8.get(11).big()
(method_t::big) $20 = {
  name = "say"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003940 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $8.get(12).big()
(method_t::big) $21 = {
  name = "name"
  types = 0x0000000100003f71 "@16@0:8"
  imp = 0x0000000100003970 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $8.get(13).big()
(method_t::big) $22 = {
  name = "setName:"
  types = 0x0000000100003f79 "v24@0:8@16"
  imp = 0x0000000100003990 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $8.get(14).big()
(method_t::big) $23 = {
  name = "age"
  types = 0x0000000100003f84 "q16@0:8"
  imp = 0x00000001000039c0 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $8.get(15).big()
(method_t::big) $24 = {
  name = "setAge:"
  types = 0x0000000100003f8c "v24@0:8q16"
  imp = 0x00000001000039e0 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $8.get(16).big()
(method_t::big) $25 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f69 "v16@0:8"
  imp = 0x0000000100003a00 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $3.ext()
(class_rw_ext_t *) $26 = nil

总结:

  • ①. 非懒加载主类是在map_images时开始加载的,无论有单个还是多个懒加载分类都是存放类的robaseMethods里的一维数组

  • ②. 编译的先后顺序会影响分类的存储顺序。 截屏2023-03-05 下午2.06.01.png

非懒加载主类以及非懒加载分类

单个非懒加载分类

  1. 主类中保持开启load类方法,只留一个分类CJPerson+CA,并在分类中开启+load类方法,在main函数中调用分类中的实例方法,并打上如下断点。
  • 图: 截屏2023-03-05 上午2.03.14.png
  1. 编译运行代码,程序执行到断点,查看一下函数调用栈。
  • 如下图所示: 截屏2023-03-03 下午4.15.36.png

  • 结论:

    非懒加载主类的调用路径为:map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift

3. 打印输出主类中方法列表信息。

  • 如下所示:
(lldb) x/6gx cls
0x100008328: 0x0000000100008300 0x0000000100721140
0x100008338: 0x0000000100719cb0 0x0024000000000000
0x100008348: 0x800060000023c560 0x0000000000000000
(lldb) p (class_data_bits_t *)0x100008348
(class_data_bits_t *) $1 = 0x0000000100008348
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000060000023c560
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000480
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100721168
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x00000001000081a0
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f6d "\U00000001"
    nonMetaclass = 0x0000000100003f6d
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f64 "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008080
  }
  baseProtocols = nil
  ivars = 0x0000000100008130
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008178
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $6 = {
  ptr = 0x0000000100008080
}
(lldb) p *$6.ptr
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 7)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "test"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003ba0 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003bd0 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "name"
  types = 0x0000000100003f7a "@16@0:8"
  imp = 0x0000000100003c00 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "setName:"
  types = 0x0000000100003f82 "v24@0:8@16"
  imp = 0x0000000100003c20 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "age"
  types = 0x0000000100003f8d "q16@0:8"
  imp = 0x0000000100003c50 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
  name = "setAge:"
  types = 0x0000000100003f95 "v24@0:8q16"
  imp = 0x0000000100003c70 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $7.get(6).big()
(method_t::big) $14 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003c90 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $3.ext()
(class_rw_ext_t *) $15 = nil
  • 结论:

    可以看到主类中仅有自己的实例方法,没有分类的方法,说明非懒加载类的加载方式一致是map_images,但是非懒加载分类懒加载分类是不同的,是没有经过编译器优化的。

  1. 过掉断点,继续执行程序,发现执行到了分类加载函数attachCategories中的断点。
  • 如下图所示: 截屏2023-03-03 下午4.26.56.png

  • 结论:

    可以发现分类的调用路径是load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

  1. 继续查看attachCategeries的情况,由于分类加载那个节已经分析了该方法会给rwe赋值ro里的baseMethods的内容,因为断点是将分类的方法拷贝到rwe之前,所以打印只有主类的7个方法。
(lldb)p rwe->methods
(method_array_t) $17 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008080
      }
      arrayAndFlag = 4295000192
    }
  }
}
(lldb) p $17.count()
(uint32_t) $18 = 7
(lldb) p $17.array()
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::array_t *) $19 = 0x0000000100008080
(lldb)
  • 结论:  
    • ①. 当实现分类中的load类方法后,就会在ObjCload_images函数调用时加载分类,跳过断点,来到attachCategories函数,此时CJPerson类中的rwe就被初始化了,并且会调用attachLists函数将CJPerson+CA分类中的方法附着到主类的方法列表中

    • ②. 当分类中方法列表为空并且附着的方法列表数量为1时(listnil并且addedCount),直接将此方法列表的地址赋值到rwemethodsC++method_array_t)的成员变量list(指针类型)中,这个list的值只是一个存储method_t一维数组的地址。

  1. 打断点进入到attachLists,这个方法会将分类的method添加到类的rwemethods里。离开这个断点回到main函数。继续调试可以发现。
(lldb) p/x [CJPerson class]
(Class) $34 = 0x0000000100008328 CJPerson
(lldb) x/6gx $34
0x100008328: 0x0000000100008300 0x0000000100721140
0x100008338: 0x0000600001704880 0x8024000100000003
0x100008348: 0x800060000023c564 0x0000000000000000
(lldb) p (class_data_bits_t *)0x100008348
(class_data_bits_t *) $35 = 0x0000000100008348
(lldb) p $35->data()
(class_rw_t *) $36 = 0x000060000023c560
(lldb) p *$36
(class_rw_t) $37 = {
  flags = 2156396544
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 105553128883425
    }
  }
  firstSubclass = nil
  nextSiblingClass = __NSUnrecognizedTaggedPointer
}
(lldb) p $37.ext()
(class_rw_ext_t *) $38 = 0x0000600000c084e0
(lldb) p *$38
(class_rw_ext_t) $39 = {
  ro = {
    ptr = 0x00000001000081a0
  }
  methods = {
    list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
       = {
        list = {
          ptr = 0x0000600000200781
        }
        arrayAndFlag = 105553118365569
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t, RawPtr> = {
       = {
        list = {
          ptr = 0x00006000002007c1
        }
        arrayAndFlag = 105553118365633
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
       = {
        list = {
          ptr = nil
        }
        arrayAndFlag = 0
      }
    }
  }
  demangledName = 0x0000000000000000
  version = 0
}
(lldb) p $39.methods
(method_array_t) $40 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000600000200781
      }
      arrayAndFlag = 105553118365569
    }
  }
}
(lldb) p $40.array()
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::array_t *) $41 = 0x0000600000200780
(lldb) p $40.count()
(uint32_t) $42 = 10
(lldb) p *$41
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::array_t) $43 = (count = 2, lists = method_list_t_authed_ptr<method_list_t>[] @ 0x0000600003612d28)
(lldb) x/4gx 0x0000600000200780
0x600000200780: 0x0000000000000002 0x00000001000081e8
0x600000200790: 0x0000000100008080 0x0000000000000000
(lldb) p (method_list_t *)0x00000001000081e8
(method_list_t *) $44 = 0x00000001000081e8
(lldb) p *$44
(method_list_t) $45 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3)
}
(lldb) p $45.get(0).big()
(method_t::big) $46 = {
  name = "ca_say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003d20 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $45.get(1).big()
(method_t::big) $47 = {
  name = "ca_eat"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003d50 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $45.get(2).big()
(method_t::big) $48 = {
  name = "test"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003d80 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p (method_list_t *)0x0000000100008080
(method_list_t *) $49 = 0x0000000100008080
(lldb) p *$49
(method_list_t) $50 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 7)
}
(lldb) p $50.get(0).big()
(method_t::big) $51 = {
  name = "say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003bd0 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $50.get(1).big()
(method_t::big) $52 = {
  name = "name"
  types = 0x0000000100003f7a "@16@0:8"
  imp = 0x0000000100003c00 (KCObjcBuild`-[CJPerson name])
}
(lldb) 
  • 结论:
    • ①. 回到主函数main会发现分类也已经添加完毕了,CJPersonrwemethods的方法数count7变成了10

    • ②. 通过x/4gx 0x0000600000200780可以知道list_array_tt内存分布情况。首个值0x0000000000000002是保存的method_list_t的值2,代表一个分类与主类。0x00000001000081e8是分类CJPerson+CA的实例方法列表地址,0x0000000100008080是主类CJPersonrobaseMethods的地址。


总结:

  • 懒加载分类是通过attachCategories添加的,而且没有编译器优化,不会保存在ro里,只能通过建立rwe后再一个个分类赋值。 截屏2023-03-05 下午2.15.59.png

多个非懒加载分类

  1. 在工程中添加CBCC,按照CACBCC顺序编译。
  • 如下图所示: 截屏2023-03-03 下午5.48.22.png
  1. 让分类CJPerson+CC保持为懒加载分类,其他分类为非懒加载分类,可以验证一下不同情况。
  • 如图:

截屏2023-03-07 下午2.37.22.png

  1. 编译运行程序,执行到如下断点,查看函数调用栈。
  • 如下所示: 截屏2023-03-07 下午2.41.34.png

  • 结论:

    在无论分类是否是懒加载分类的情况下,非懒加载类的加载方式还是map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift截屏2023-03-03 下午4.26.56.png

  1. 打印类中的方法列表中的数据,可以发现的是编译器并未将分类中的数据附着到主类中去。
  • 打印信息:
 (lldb) x/6gx cls
0x1000084b8: 0x0000000100008490 0x0000000100721140
0x1000084c8: 0x0000000100719cb0 0x0024000000000000
0x1000084d8: 0x80006000002108a0 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000084d8
(class_data_bits_t *) $1 = 0x00000001000084d8
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00006000002108a0
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000480
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100721168
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x00000001000081a0
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f68 "\U00000001"
    nonMetaclass = 0x0000000100003f68
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f5f "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008080
  }
  baseProtocols = nil
  ivars = 0x0000000100008130
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008178
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $6 = {
  ptr = 0x0000000100008080
}
(lldb) p *$6.ptr
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 7)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003890 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x00000001000038c0 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "name"
  types = 0x0000000100003f7b "@16@0:8"
  imp = 0x00000001000038f0 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "setName:"
  types = 0x0000000100003f83 "v24@0:8@16"
  imp = 0x0000000100003910 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "age"
  types = 0x0000000100003f8e "q16@0:8"
  imp = 0x0000000100003940 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
  name = "setAge:"
  types = 0x0000000100003f96 "v24@0:8q16"
  imp = 0x0000000100003960 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $7.get(6).big()
(method_t::big) $14 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003980 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) 
(lldb) p $3.ext()
(class_rw_ext_t *) $15 = nil
  1. 过掉断点,程序卡在了attachCategories函数函数中,此时打印mCount的值1,也就是当前加载的分类的数量为1。而这个函数会调用3次,正好对应3个分类:CACBCC
  • 如下图所示: 截屏2023-03-07 下午2.48.57.png

  • 第一次进入打印分类CA信息:

(lldb) p cats_list
(const locstamped_category_t *) $16 = 0x00007ff7bfefd648
(lldb) p cats_list->cat
(category_t *const) $17 = 0x0000000100008298
(lldb) p *$17
(category_t) $18 = {
  name = 0x0000000100003f6a "CA"
  cls = 0x00000001000084b8
  instanceMethods = {
    ptr = 0x00000001000081e8
  }
  classMethods = {
    ptr = 0x0000000100008238
  }
  protocols = nil
  instanceProperties = 0x0000000100008270
  _classProperties = nil
}
(lldb) 
  • 第二次进入打印分类CB信息:
(lldb) p cats_list
(const locstamped_category_t *) $20 = 0x00007ff7bfefd648
(lldb) p *$20
(const locstamped_category_t) $21 = {
  cat = 0x0000000100008360
  hi = 0x0000600002611860
}
(lldb) p $21.cat
(category_t *const) $22 = 0x0000000100008360
(lldb) p *$22
(category_t) $23 = {
  name = 0x0000000100003f6d "CB"
  cls = 0x00000001000084b8
  instanceMethods = {
    ptr = 0x00000001000082d8
  }
  classMethods = {
    ptr = 0x0000000100008328
  }
  protocols = nil
  instanceProperties = nil
  _classProperties = nil
}
(lldb)
  • 第三次进入打印分类CC信息:
(lldb) p cats_list
(const locstamped_category_t *) $24 = 0x00007ff7bfefd648
(lldb) p *$24
(const locstamped_category_t) $25 = {
  cat = 0x0000000100008428
  hi = 0x0000600002611860
}
(lldb) p $25.cat
(category_t *const) $26 = 0x0000000100008428
(lldb) p *$26
(category_t) $27 = {
  name = 0x0000000100003f70 "CC"
  cls = 0x00000001000084b8
  instanceMethods = {
    ptr = 0x00000001000083a0
  }
  classMethods = {
    ptr = 0x0000000100008408
  }
  protocols = nil
  instanceProperties = nil
  _classProperties = nil
}
(lldb) 
  1. 加载完三个分类,回到main函数,此时再打印CJPersonrwe的情况,可以知道分类方法的排列情况。
  • 打印类的rwe情况:
lldb) p/x [CJPerson class]
(Class) $28 = 0x00000001000084b8 CJPerson
(lldb) x/6gx $28
0x1000084b8: 0x0000000100008490 0x0000000100721140
0x1000084c8: 0x00006000017101c0 0x8024000100000003
0x1000084d8: 0x80006000002108a4 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000084d8
(class_data_bits_t *) $29 = 0x00000001000084d8
(lldb) p $29->data()
(class_rw_t *) $30 = 0x00006000002108a0
(lldb) p *$30
(class_rw_t) $31 = {
  flags = 2156396544
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 105553128883569
    }
  }
  firstSubclass = nil
  nextSiblingClass = __NSUnrecognizedTaggedPointer
}
(lldb) p $31.ext()
(class_rw_ext_t *) $32 = 0x0000600000c08570
(lldb) p *$32
(class_rw_ext_t) $33 = {
  ro = {
    ptr = 0x00000001000081a0
  }
  methods = {
    list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
       = {
        list = {
          ptr = 0x0000600000c085d1
        }
        arrayAndFlag = 105553128883665
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t, RawPtr> = {
       = {
        list = {
          ptr = 0x000060000020f5e1
        }
        arrayAndFlag = 105553118426593
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
       = {
        list = {
          ptr = nil
        }
        arrayAndFlag = 0
      }
    }
  }
  demangledName = 0x0000000000000000
  version = 0
}
(lldb) p $33.methods
(method_array_t) $34 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000600000c085d1
      }
      arrayAndFlag = 105553128883665
    }
  }
}
(lldb) p $34.count()
(uint32_t) $35 = 17
(lldb) p $34.array()
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::array_t *) $36 = 0x0000600000c085d0
(lldb) x/8gx $36
0x600000c085d0: 0x0000000000000004 0x00000001000083a0
0x600000c085e0: 0x00000001000082d8 0x00000001000081e8
0x600000c085f0: 0x0000000100008080 0x0000000000000000
0x600000c08600: 0x0000000000000004 0x0000000100008408
(lldb) p (method_list_t *)0x00000001000083a0
(method_list_t *) $37 = 0x00000001000083a0
(lldb) p *$37
(method_list_t) $38 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 4)
}
(lldb) p $38.get(0).big()
(method_t::big) $39 = {
  name = "say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003c20 (KCObjcBuild`-[CJPerson(CC) say])
}
(lldb) p $38.get(1).big()
(method_t::big) $40 = {
  name = "cc_say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003bc0 (KCObjcBuild`-[CJPerson(CC) cc_say])
}
(lldb) p $38.get(2).big()
(method_t::big) $41 = {
  name = "cc_eat"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003bf0 (KCObjcBuild`-[CJPerson(CC) cc_eat])
}
(lldb) p $38.get(3).big()
(method_t::big) $42 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003c50 (KCObjcBuild`-[CJPerson(CC) test])
}
(lldb) p (method_list_t *)0x00000001000082d8
(method_list_t *) $43 = 0x00000001000082d8
(lldb) p *$43
(method_list_t) $44 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3)
}
(lldb) p $44.get(0).big()
(method_t::big) $45 = {
  name = "cb_say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003b00 (KCObjcBuild`-[CJPerson(CB) cb_say])
}
(lldb) p $44.get(1).big()
(method_t::big) $46 = {
  name = "cb_eat"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003b30 (KCObjcBuild`-[CJPerson(CB) cb_eat])
}
(lldb) p $44.get(2).big()
(method_t::big) $47 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003b60 (KCObjcBuild`-[CJPerson(CB) test])
}
(lldb) p (method_list_t *)0x00000001000081e8
(method_list_t *) $48 = 0x00000001000081e8
(lldb) p *$48
(method_list_t) $49 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3)
}
(lldb) p $49.get(0).big()
(method_t::big) $50 = {
  name = "ca_say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003a10 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $49.get(1).big()
(method_t::big) $51 = {
  name = "ca_eat"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003a40 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $49.get(2).big()
(method_t::big) $52 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003a70 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p (method_list_t *)0x0000000100008080
(method_list_t *) $53 = 0x0000000100008080
(lldb) p *$53
(method_list_t) $54 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 7)
}
(lldb) p $54.get(0).big()
(method_t::big) $55 = {
  name = "say"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x00000001000038c0 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $54.get(1).big()
(method_t::big) $56 = {
  name = "name"
  types = 0x0000000100003f7b "@16@0:8"
  imp = 0x00000001000038f0 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $54.get(2).big()
(method_t::big) $57 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003980 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $54.get(3).big()
(method_t::big) $58 = {
  name = "setName:"
  types = 0x0000000100003f83 "v24@0:8@16"
  imp = 0x0000000100003910 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $54.get(4).big()
(method_t::big) $59 = {
  name = "test"
  types = 0x0000000100003f73 "v16@0:8"
  imp = 0x0000000100003890 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $54.get(5).big()
(method_t::big) $60 = {
  name = "age"
  types = 0x0000000100003f8e "q16@0:8"
  imp = 0x0000000100003940 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $54.get(6).big()
(method_t::big) $61 = {
  name = "setAge:"
  types = 0x0000000100003f96 "v24@0:8q16"
  imp = 0x0000000100003960 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) 
  • 结论:

    那么为什么会这样一个一个的加载分类呢?

    • 原因:在于load_categories_nolock函数中获取到CJPerson的所有分类,然后遍历分类,一个接一个的调用attachCategories函数的。

总结:

  • 当主类是非懒加载类,主类只会在map_images映射镜像时加载的;而分类是懒加载分类时,就会被编译器优化到ro里;而分类非懒加载分类,无论多少都是通过load_images加载镜像时,通过load_Allcategeries一个接一个加载。

  • 所有分类数据加载完毕之后,打印一下这个方法列表数组的地址以及其中最后一个方法列表的数据,如下图所示: 截屏2023-03-05 下午2.28.29.png

懒加载主类以及非懒加载分类

单个非懒加载分类

  1. 将主类CJPerson屏蔽+load类方法,变回懒加载主类;而分类只保留编译一个CJPerson+CA非懒加载分类
  • 如图: 截屏2023-03-03 下午6.24.38.png
  1. 编译执行程序,程序卡在断点,查看函数调用栈。
  • 如图所示: 截屏2023-03-03 下午6.27.19.png

  • 结论:

    此时,主类CJPerson虽然是懒加载类,但是没有通过消息慢速查找时加载主类数据,而是跟非懒加载类时一样。主要原因是懒加载类非懒加载分类影响变成了非懒加载类

  1. 打印主类中ro的方法列表方法。
  • 如下所示:
(lldb) x/6gx cls
0x1000082b8: 0x0000000100008290 0x0000000100721140
0x1000082c8: 0x0000000100719cb0 0x0024000000000000
0x1000082d8: 0x800060000020ada0 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000082d8
(class_data_bits_t *) $1 = 0x00000001000082d8
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000060000020ada0
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000280
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x0000000100721168
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x00000001000080d8
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f6c "\U00000001"
    nonMetaclass = 0x0000000100003f6c
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f63 "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008198
  }
  baseProtocols = nil
  ivars = 0x0000000100008090
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008000
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods.ptr
(method_list_t *const) $6 = 0x0000000100008198
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 10)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "ca_say"
  types = 0x0000000100003f71 "v16@0:8"
  imp = 0x0000000100003d30 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "ca_eat"
  types = 0x0000000100003f71 "v16@0:8"
  imp = 0x0000000100003d60 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "test"
  types = 0x0000000100003f71 "v16@0:8"
  imp = 0x0000000100003d90 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "test"
  types = 0x0000000100003f71 "v16@0:8"
  imp = 0x0000000100003bb0 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "say"
  types = 0x0000000100003f71 "v16@0:8"
  imp = 0x0000000100003be0 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
  name = "name"
  types = 0x0000000100003f79 "@16@0:8"
  imp = 0x0000000100003c10 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $7.get(6).big()
(method_t::big) $14 = {
  name = "setName:"
  types = 0x0000000100003f81 "v24@0:8@16"
  imp = 0x0000000100003c30 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $7.get(7).big()
(method_t::big) $15 = {
  name = "age"
  types = 0x0000000100003f8c "q16@0:8"
  imp = 0x0000000100003c60 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $7.get(8).big()
(method_t::big) $16 = {
  name = "setAge:"
  types = 0x0000000100003f94 "v24@0:8q16"
  imp = 0x0000000100003c80 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $7.get(9).big()
(method_t::big) $17 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f71 "v16@0:8"
  imp = 0x0000000100003ca0 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $3.ext()
(class_rw_ext_t *) $18 = nil
(lldb) 
  • 结论:

    可以看到的是,编译器已经将分类中的数据附着到了主类中,过掉断点,程序并不会执行attachCategories函数,而且编译器已经把单个非懒加载分类优化进入类的ro里。


总结:

  • 懒加载类会受单个非懒加载分类影响变成非懒加载类,而非懒加载分类的数据也会优化到ro中,从而不需要走attachCategories;跟非懒加载类+懒加载分类一样。 截屏2023-03-05 下午2.33.29.png

多个非懒加载分类

  1. 在工程中添加CBCC类,按照CCCBCA的编译顺序。
  • 如下图所示: 截屏2023-03-03 下午6.40.34.png
  1. 并且只在CACB中开启+load类方法为非懒加载分类
  • 图: 截屏2023-03-03 下午6.38.13.png
  1. 运行程序,程序执行到断点,查看函数调动栈。
  • 如下所示: 截屏2023-03-05 上午2.16.27.png

  • 结论:

    • ①. 在懒加载类情况下,多个非懒加载分类跟单个是不同的。不再是map_images时,而是load_images时才加载主类数据。

    • ②. 主类加载调用流程:load_images -> prepare_load_methods -> realizeClassWithoutSwift

  1. 打印类中的方法列表数据。
  • 如下图所示:
(lldb) x/6gx cls
0x1000084a0: 0x0000000100008478 0x0000000100721140
0x1000084b0: 0x0000000100719cb0 0x0024000000000000
0x1000084c0: 0x8000600000231420 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000084c0
(class_data_bits_t *) $1 = 0x00000001000084c0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000600000231420
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000456
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007ff856e78200
}
(lldb) p $3.ro()
(const class_ro_t *) $4 = 0x0000000100008188
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
   = {
    ivarLayout = 0x0000000100003f67 "\U00000001"
    nonMetaclass = 0x0000000100003f67
  }
  name = {
    std::__1::atomic<const char *> = "CJPerson" {
      Value = 0x0000000100003f5e "CJPerson"
    }
  }
  baseMethods = {
    ptr = 0x0000000100008068
  }
  baseProtocols = nil
  ivars = 0x0000000100008118
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008160
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $5.baseMethods
(const WrappedPtr<method_list_t, method_list_t::Ptrauth>) $6 = {
  ptr = 0x0000000100008068
}
(lldb) p *$6.ptr
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 7)
}
(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "test"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x00000001000038a0 (KCObjcBuild`-[CJPerson test])
}
(lldb) p $7.get(1).big()
(method_t::big) $9 = {
  name = "say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x00000001000038d0 (KCObjcBuild`-[CJPerson say])
}
(lldb) p $7.get(2).big()
(method_t::big) $10 = {
  name = "name"
  types = 0x0000000100003f7a "@16@0:8"
  imp = 0x0000000100003900 (KCObjcBuild`-[CJPerson name])
}
(lldb) p $7.get(3).big()
(method_t::big) $11 = {
  name = "setName:"
  types = 0x0000000100003f82 "v24@0:8@16"
  imp = 0x0000000100003920 (KCObjcBuild`-[CJPerson setName:])
}
(lldb) p $7.get(4).big()
(method_t::big) $12 = {
  name = "age"
  types = 0x0000000100003f8d "q16@0:8"
  imp = 0x0000000100003950 (KCObjcBuild`-[CJPerson age])
}
(lldb) p $7.get(5).big()
(method_t::big) $13 = {
  name = "setAge:"
  types = 0x0000000100003f95 "v24@0:8q16"
  imp = 0x0000000100003970 (KCObjcBuild`-[CJPerson setAge:])
}
(lldb) p $7.get(6).big()
(method_t::big) $14 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003990 (KCObjcBuild`-[CJPerson .cxx_destruct])
}
(lldb) p $3.ext()
(class_rw_ext_t *) $15 = nil
  • 结论:

    这种情况,编译器也没有将分类数据附着到主类中。

  1. 过掉断点,程序执行到了attachToClass函数中。
  • 如下图所示: 截屏2023-03-03 下午6.48.56.png
  1. 过掉断点,程序执行到attachCategories函数,打印mcount的值3,以及分类信息。
  • 如下所示:
(lldb) p cats_count
(uint32_t) $15 = 3
(lldb) p cats_list
(const locstamped_category_t *) $16 = 0x0000600001704200
(lldb) p *$16
(const locstamped_category_t) $17 = {
  cat = 0x0000000100008258
  hi = 0x0000600002605920
}
(lldb) p $17.cat
(category_t *const) $18 = 0x0000000100008258
(lldb) p *$18
(category_t) $19 = {
  name = 0x0000000100003f69 "CC"
  cls = 0x00000001000084a0
  instanceMethods = {
    ptr = 0x00000001000081d0
  }
  classMethods = {
    ptr = 0x0000000100008238
  }
  protocols = nil
  instanceProperties = nil
  _classProperties = nil
}
(lldb) p cats_list[1]
(const locstamped_category_t) $20 = {
  cat = 0x0000000100008320
  hi = 0x0000600002605920
}
(lldb) p *$20.cat
(category_t) $21 = {
  name = 0x0000000100003f6c "CB"
  cls = 0x00000001000084a0
  instanceMethods = {
    ptr = 0x0000000100008298
  }
  classMethods = {
    ptr = 0x00000001000082e8
  }
  protocols = nil
  instanceProperties = nil
  _classProperties = nil
}
(lldb) p cats_list[2]
(const locstamped_category_t) $22 = {
  cat = 0x0000000100008410
  hi = 0x0000600002605920
}
(lldb) p *$22.cat
(category_t) $23 = {
  name = 0x0000000100003f6f "CA"
  cls = 0x00000001000084a0
  instanceMethods = {
    ptr = 0x0000000100008360
  }
  classMethods = {
    ptr = 0x00000001000083b0
  }
  protocols = nil
  instanceProperties = 0x00000001000083e8
  _classProperties = nil
}
(lldb) 
  • 结论:

    懒加载类情况下,受非懒加载分类影响下会变成非懒加载类,但是多个非懒加载分类会导致主类的加载延时到load_images时进行,而且通过attachToClass一次性将三个分类按照编译顺序加入类中,正好对应了分类的加载第三条路径。

  1. 离开这个断点回到main函数。
  • 打印完整类信息:
(lldb) p [CJPerson class]
(Class) $24 = CJPerson
(lldb) x/6gx $24
0x1000084a0: 0x0000000100008478 0x0000000100721140
0x1000084b0: 0x0000600001708040 0x8024000100000003
0x1000084c0: 0x8000600000231424 0x0000000000000000
(lldb) p (class_data_bits_t *)0x1000084c0
(class_data_bits_t *) $25 = 0x00000001000084c0
(lldb) p $25->data()
(class_rw_t *) $26 = 0x0000600000231420
(lldb) p *$26
(class_rw_t) $27 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 105553128932769
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSProcessInfo
}
(lldb) p $27.ext()
(class_rw_ext_t *) $28 = 0x0000600000c145a0
(lldb) p *$28
(class_rw_ext_t) $29 = {
  ro = {
    ptr = 0x0000000100008188
  }
  methods = {
    list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
       = {
        list = {
          ptr = 0x0000600000c08031
        }
        arrayAndFlag = 105553128882225
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t, RawPtr> = {
       = {
        list = {
          ptr = 0x0000600000238001
        }
        arrayAndFlag = 105553118593025
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
       = {
        list = {
          ptr = nil
        }
        arrayAndFlag = 0
      }
    }
  }
  demangledName = 0x0000000000000000
  version = 0
}
(lldb) p $29.methods
(method_array_t) $30 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000600000c08031
      }
      arrayAndFlag = 105553128882225
    }
  }
}
(lldb) p $30.count()
(uint32_t) $31 = 17
(lldb) p $30.array()
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::array_t *) $32 = 0x0000600000c08030
(lldb) x/6gx $32
0x600000c08030: 0x0000000000000004 0x0000000100008360
0x600000c08040: 0x0000000100008298 0x00000001000081d0
0x600000c08050: 0x0000000100008068 0x0000000000000000
(lldb) p (method_list_t *)0x0000000100008360
(method_list_t *) $33 = 0x0000000100008360
(lldb) p *$33
(method_list_t) $34 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3)
}
(lldb) p $34.get(0).big()
(method_t::big) $35 = {
  name = "ca_say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003c00 (KCObjcBuild`-[CJPerson(CA) ca_say])
}
(lldb) p $34.get(1).big()
(method_t::big) $36 = {
  name = "ca_eat"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003c30 (KCObjcBuild`-[CJPerson(CA) ca_eat])
}
(lldb) p $34.get(2).big()
(method_t::big) $37 = {
  name = "test"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003c60 (KCObjcBuild`-[CJPerson(CA) test])
}
(lldb) p (method_list_t *)0x0000000100008298
(method_list_t *) $38 = 0x0000000100008298
(lldb) p *$38
(method_list_t) $39 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3)
}
(lldb) p $39.get(0).big()
(method_t::big) $40 = {
  name = "cb_say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003b10 (KCObjcBuild`-[CJPerson(CB) cb_say])
}
(lldb) p $39.get(1).big()
(method_t::big) $41 = {
  name = "cb_eat"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003b40 (KCObjcBuild`-[CJPerson(CB) cb_eat])
}
(lldb) p $39.get(2).big()
(method_t::big) $42 = {
  name = "test"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003b70 (KCObjcBuild`-[CJPerson(CB) test])
}
(lldb) p (method_list_t *)0x00000001000081d0
(method_list_t *) $43 = 0x00000001000081d0
(lldb) p *$43
(method_list_t) $44 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 4)
}
(lldb) p $44.get(0).big()
(method_t::big) $45 = {
  name = "say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003a50 (KCObjcBuild`-[CJPerson(CC) say])
}
(lldb) p $44.get(1).big()
(method_t::big) $46 = {
  name = "cc_say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x00000001000039f0 (KCObjcBuild`-[CJPerson(CC) cc_say])
}
(lldb) p $44.get(2).big()
(method_t::big) $47 = {
  name = "cc_eat"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003a20 (KCObjcBuild`-[CJPerson(CC) cc_eat])
}
(lldb) p $44.get(3).big()
(method_t::big) $48 = {
  name = "test"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003a80 (KCObjcBuild`-[CJPerson(CC) test])
}
(lldb) p (method_list_t *)0x0000000100008068
(method_list_t *) $49 = 0x0000000100008068
(lldb) p *$49
(method_list_t) $50 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 7)
}
(lldb) p $50.get(0).big()
(method_t::big) $51 = {
  name = "say"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x00000001000038d0 (KCObjcBuild`-[CJPerson say])
}
(lldb) 

总结:

  • 懒加载类情况下,受多个非懒加载分类影响下导致主类的加载延时到load_images时进行,而分类刚好通过分类的加载的第三条路径一次性加载完。

  • 根据以上的探究数据,类的方法列表数组结构图如下: 截屏2023-03-05 下午2.43.27.png

结论:

根据以上探究,实际上分类的加载共有以下的5种情况:

  1. 主类懒加载类,如果所有分类也为懒加载分类,则会在类第一次发送消息时加载类数据,并且此时所有分类数据已经附着到了主类中,主类以及分类的方法列表、属性列表协议列表都是以一级指针的方式存储在ro中。
  • 主类与分类的函数调用栈:objc_msgSend->lookupImpOrForward->realizeClassWithoutSwift
  1. 主类懒加载类,但只有一个非懒加载分类时,这时就会以非懒加载的形式加载主类,并且此时所有分类数据已经附着到了主类中,主类以及分类的方法列表、属性列表协议列表都是以一级指针的方式存储在ro中。
  • 主类与分类的函数调用栈:map_images->map_images_nolock->_read_images->realizeClassWithoutSwift
  1. 主类懒加载类,但有多个非懒加载的分类时,这时就会以非懒加载的形式加载主类,并且会调用attachCategories函数将所有分类数据一次性全部附着到主类中,主类以及分类的方法列表、属性列表以及协议列表都是以二级指针的方式存储在rwe中。
  • 主类与分类函数调用栈:load_images->prepare_load_methods->realizeClassWithoutSwift->methodizeClass->attachToClass->attachCategories
  1. 主类非懒加载类,所有分类均为懒加载类时,这种情况与2中一致,主类以及分类的方法列表、属性列表以及协议列表都是以一级指针的方式存储在ro中。   
  • 主类与分类的函数调用栈:map_images->map_images_nolock->_read_images->realizeClassWithoutSwift
  1. 主类非懒加载类,分类中存在至少一个非懒加载类时,除了会调用realizeClassWithoutSwift函数之外,还会调用attachCategories函数将分类数据一个接一个的按照编译顺序附着到主类中,主类以及分类的方法列表、属性列表以及协议列表都是以二级指针的方式存储在rwe中。   
  • 类的调用栈:map_images->map_images_nolock->_read_images->realizeClassWithoutSwift
  • 函数调用栈:load_images->loadAllCategories->load_categories_nolock->attachCategories
本类加载时机分类加载时机存储位置(ro/rwe)
本类非懒加载,分类非懒加载map_imagesload_images->loadAllCategoriesrwe
本类非懒加载,分类懒加载map_images编译ro
本类懒加载,分类懒加载消息慢速查找编译ro
本类懒加载,分类非懒加载map_images编译ro
本类懒加载,分类非懒加载 > 1load_images->prepare_load_methods->realizeClassWithoutSwiftattachToClass->attachCategoriesrwe
本类懒加载,分类懒加载=N&分类非懒加载=1map_images编译ro
本类非懒加载,分类非懒加载>0map_imagesload_images->loadAllCategoriesrwe
本类非懒加载,分类懒加载>0map_images编译ro