在dyld4流程下中,咱们探求和剖析了map_imagesload_images这两个函数;可是没有对类的加载做出具体解释,本文就探讨下类的加载。

一、类的加载

在前一篇文章中read_images函数中,咱们提到非懒加载类的加载是经过调用realizeClassWithoutSwift来完成的,那咱们就首要看下这个函数源码。

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

  • 定论:

    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状况:

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

  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

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

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

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

  • ③. 而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();
    }
}
  • 调企图:

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

  • 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++代码。
  • 图:

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

  • 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数据(加载过多个分类),那么便是多对多的流程。

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

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

经过对类的加载分类的加载源码剖析,咱们能够依据是否懒加载的状况?大致将类与分类的加载分为以下八种状况:

类+分类 单个分类 多个分类 单个分类+完成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里设置。
  • 图:
    OC底层原理(十三):类与分类加载
  1. 将主类CJPerson与分类CJPerson+CA里的类办法+load悉数屏蔽起来。
  • 图:
    OC底层原理(十三):类与分类加载
  1. 运转可调试源码,会卡在CJPerson第一次进行音讯慢速查找时调用主类加载函数realizeClassWithoutSwift
  • 图:
    OC底层原理(十三):类与分类加载
  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函数将分类中的办法附着到主类中,而是编译器完成了这部分操作,而且主类的办法列表一维的,次序是分类中的办法在主类办法前。

  • ②. 分类数据加载后主类办法类别内存结构示意图如下所示:

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

多个懒加载分类

假如主类的分类许多,懒加载主类与多个懒加载分类又是怎么处理的呢?

  1. XcodeCompile Source里设置。
  • 如图:
    OC底层原理(十三):类与分类加载
  1. 将其他两个分类CJPerson+CBCJPerson+CC里的类办法+load悉数屏蔽起来。
  • 图:
    OC底层原理(十三):类与分类加载
  1. 运转可调试源码,跟单个懒加载分类相同都会停在CJPerson第一次进行音讯慢速查找时调用主类加载函数realizeClassWithoutSwift
  • 调企图:
    OC底层原理(十三):类与分类加载
  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) 

总结:

  • ①. 当主类都为懒加载办法时,不管懒加载分类有多少个都是第一次进行音讯慢速查找时,是编译器完成了分类中的办法附着到主类中,而且主类的办法列表一维的,次序是分类中的办法在主类办法前。

  • ②. 能够发现,主类办法列表中的办法次序是与分类的编译次序有关的,每编译一个分类时,会将其所有的办法插入到其主类办法列表的最前面。示意图如下所示:

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

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

单个懒加载分类

主类中敞开+load类办法,只留一个CJPerson分类CA

  1. 首要在XcodeCompile Source里设置成单个分类的办法,这个跟上面相同。
  • 设置图:
    OC底层原理(十三):类与分类加载
  1. 将主类CJPerson设置成非懒加载类
  • 图:
    OC底层原理(十三):类与分类加载
  1. 编译运转代码,程序履行到断点,检查一下函数调用栈。
  • 如图所示:

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

  • 定论:

    此刻非懒加载类的途径为: 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文件时的优化作业。

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

多个懒加载分类

  1. 重新把剩下的两个分类加回XcodeComplie Source里,为了验证之前说尾插入分类而更改它们次序下,依照CCCACB的先后编译次序。
  • 如下图所示:
    OC底层原理(十三):类与分类加载
  1. 主类非懒加载分类CCCACB都是懒加载
    OC底层原理(十三):类与分类加载
  2. 编译运转程序,程序履行到断点,检查函数调用栈。
  • 如下图所示:

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

  • 定论:

    非懒加载类的途径为: 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里的一维数组

  • ②. 编译的先后次序会影响分类的存储次序。

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

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

单个非懒加载分类

  1. 主类中坚持敞开load类办法,只留一个分类CJPerson+CA,并在分类中敞开+load类办法,在main函数中调用分类中的实例办法,并打上如下断点。
  • 图:
    OC底层原理(十三):类与分类加载
  1. 编译运转代码,程序履行到断点,检查一下函数调用栈。
  • 如下图所示:

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

  • 定论:

    非懒加载主类的调用途径为: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中的断点。
  • 如下图所示:

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

  • 定论:

    能够发现分类的调用途径是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后再一个个分类赋值。
    OC底层原理(十三):类与分类加载

多个非懒加载分类

  1. 在工程中增加CBCC,依照CACBCC次序编译。
  • 如下图所示:
    OC底层原理(十三):类与分类加载
  1. 让分类CJPerson+CC坚持为懒加载分类,其他分类为非懒加载分类,能够验证一下不同状况。
  • 如图:

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

  1. 编译运转程序,履行到如下断点,检查函数调用栈。
  • 如下所示:

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

  • 定论:

    在不管分类是否是懒加载分类的状况下,非懒加载类的加载办法仍是map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift

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

  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
  • 如下图所示:

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

  • 第一次进入打印分类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) 
  • 第2次进入打印分类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一个接一个加载。

  • 所有分类数据加载结束之后,打印一下这个办法列表数组的地址以及其间最终一个办法列表的数据,如下图所示:

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

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

单个非懒加载分类

  1. 将主类CJPerson屏蔽+load类办法,变回懒加载主类;而分类只保存编译一个CJPerson+CA非懒加载分类
  • 如图:
    OC底层原理(十三):类与分类加载
  1. 编译履行程序,程序卡在断点,检查函数调用栈。
  • 如图所示:

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

  • 定论:

    此刻,主类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;跟非懒加载类+懒加载分类相同。
    OC底层原理(十三):类与分类加载

多个非懒加载分类

  1. 在工程中增加CBCC类,依照CCCBCA的编译次序。
  • 如下图所示:
    OC底层原理(十三):类与分类加载
  1. 而且只在CACB中敞开+load类办法为非懒加载分类
  • 图:
    OC底层原理(十三):类与分类加载
  1. 运转程序,程序履行到断点,检查函数调集栈。
  • 如下所示:

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

  • 定论:

    • ①. 在懒加载类状况下,多个非懒加载分类跟单个是不同的。不再是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函数中。
  • 如下图所示:
    OC底层原理(十三):类与分类加载
  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时进行,而分类刚好经过分类的加载的第三条途径一次性加载完。

  • 依据以上的探求数据,类的办法列表数组结构图如下:

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

定论:

依据以上探求,实际上分类的加载共有以下的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_images load_images->loadAllCategories rwe
本类非懒加载,分类懒加载 map_images 编译 ro
本类懒加载,分类懒加载 音讯慢速查找 编译 ro
本类懒加载,分类非懒加载 map_images 编译 ro
本类懒加载,分类非懒加载 > 1 load_images->prepare_load_methods->realizeClassWithoutSwift attachToClass->attachCategories rwe
本类懒加载,分类懒加载=N&分类非懒加载=1 map_images 编译 ro
本类非懒加载,分类非懒加载>0 map_images load_images->loadAllCategories rwe
本类非懒加载,分类懒加载>0 map_images 编译 ro