OC底层原理14 - 类的加载之分类

类的懒加载和非懒加载

OC底层原理13 - 类的加载过程的分析中知道realizeClassWithoutSwift函数的主要作用有:

  • 首次初始化类
  • 分配读写数据rw
  • 返回类的真实结构
  • 将类标记为已加载rw->flags = RW_REALIZED|RW_REALIZING|isMeta;

因此,类是否完成加载即可通过查看是否调用realizeClassWithoutSwift函数.

类的懒加载

类的懒加载:在类中未实现了+load方法,将MachO中类的数据加载至内存的过程推迟至第一次方法调用时,在进行消息查找的过程中调用realizeClassWithoutSwift函数完成.

当类未实现+load方法时,其调用堆栈如下:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
* frame #0: 0x00000001002f6965 libobjc.A.dylib`realizeClassWithoutSwift(cls=HQPerson, previously=0x0000000000000000) at objc-runtime-new.mm:2659:57
frame #1: 0x0000000100313530 libobjc.A.dylib`realizeClassMaybeSwiftMaybeRelock(cls=HQPerson, lock=0x000000010035a080, leaveLocked=false) at objc-runtime-new.mm:2928:9
frame #2: 0x000000010030354f libobjc.A.dylib`realizeClassMaybeSwiftAndUnlock(cls=HQPerson, lock=0x000000010035a080) at objc-runtime-new.mm:2945:12
frame #3: 0x00000001002f6200 libobjc.A.dylib`initializeAndMaybeRelock(cls=0x0000000100008298, inst=0x00000001000082c0, lock=0x000000010035a080, leaveLocked=true) at objc-runtime-new.mm:2244:19
frame #4: 0x00000001003170ea libobjc.A.dylib`initializeAndLeaveLocked(cls=0x0000000100008298, obj=0x00000001000082c0, lock=0x000000010035a080) at objc-runtime-new.mm:2269:12
frame #5: 0x00000001002ff7a5 libobjc.A.dylib`realizeAndInitializeIfNeeded_locked(inst=0x00000001000082c0, cls=0x0000000100008298, initialize=true) at objc-runtime-new.mm:6390:15
frame #6: 0x00000001002ff23a libobjc.A.dylib`lookUpImpOrForward(inst=0x00000001000082c0, sel="alloc", cls=0x0000000100008298, behavior=11) at objc-runtime-new.mm:6500:11
frame #7: 0x00000001002d7c1b libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1153
frame #8: 0x000000010033c057 libobjc.A.dylib`objc_alloc_init [inlined] callAlloc(cls=HQPerson, checkNil=true, allocWithZone=false) at NSObject.mm:1937:12
frame #9: 0x000000010033bfae libobjc.A.dylib`objc_alloc_init(cls=HQPerson) at NSObject.mm:1967
frame #10: 0x00000001000039c9 HQObjc`main(argc=1, argv=0x00007ffeefbff4d0) at main.m:157:28 [opt]
frame #11: 0x00007fff6f1eccc9 libdyld.dylib`start + 1

类的非懒加载

类的非懒加载:在类中实现了+load方法,将MachO中类的数据加载至内存的过程是在map_images中调用realizeClassWithoutSwift函数完成.

当类实现+load方法时,其调用堆栈如下:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
* frame #0: 0x00000001002f6965 libobjc.A.dylib`realizeClassWithoutSwift(cls=HQPerson, previously=0x0000000000000000) at objc-runtime-new.mm:2659:57
frame #1: 0x00000001002d41d6 libobjc.A.dylib`_read_images(hList=0x00007ffeefbf36d0, hCount=261, totalClasses=22057, unoptimizedTotalClasses=22057) at objc-runtime-new.mm:3835:13
frame #2: 0x00000001002d3008 libobjc.A.dylib`map_images_nolock(mhCount=261, mhPaths=0x00007ffeefbf4e00, mhdrs=0x00007ffeefbf4060) at objc-os.mm:596:9
frame #3: 0x00000001002d2b63 libobjc.A.dylib`map_images(count=261, paths=0x00007ffeefbf4e00, mhdrs=0x00007ffeefbf4060) at objc-runtime-new.mm:3165:12
frame #4: 0x0000000100019668 dyld`dyld::notifyBatchPartial(dyld_image_states, bool, char const* (*)(dyld_image_states, unsigned int, dyld_image_info const*), bool, bool) + 1749
frame #5: 0x0000000100019809 dyld`dyld::registerObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*)) + 63
frame #6: 0x00007fff6f1d3ad1 libdyld.dylib`_dyld_objc_notify_register + 113
frame #7: 0x00000001002d261a libobjc.A.dylib`_objc_init at objc-os.mm:939:5
frame #8: 0x000000010044f0bc libdispatch.dylib`_os_object_init + 13
frame #9: 0x000000010045fafc libdispatch.dylib`libdispatch_init + 282
frame #10: 0x00007fff6c144791 libSystem.B.dylib`libSystem_initializer + 220
frame #11: 0x000000010002f1d3 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
frame #12: 0x000000010002f5de dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
frame #13: 0x0000000100029ffb dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
frame #14: 0x0000000100029f66 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
frame #15: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
frame #16: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
frame #17: 0x0000000100016662 dyld`dyld::initializeMainExecutable() + 129
frame #18: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
frame #19: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
frame #20: 0x0000000100015025 dyld`_dyld_start + 37

分类

分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体category_t的指针.

struct category_t {
    //分类名称
    const char *name;
    //分类所属的类
    classref_t cls;
    //分类中定义的实例方法列表
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
    //分类中定义的类方法列表
    WrappedPtr<method_list_t, PtrauthStrip> 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;
};

接下来通过clang将HQPerson+HQA.m编译成C++代码来看看category_t结构.
clang语句:clang -rewrite-objc HQPerson+HQA.m -o HQPerso+HQA.cpp

static struct _category_t _OBJC_$_CATEGORY_HQPerson_$_HQA __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "HQPerson",    //分类名称
    0, // &OBJC_CLASS_$_HQPerson,  //分类所属的类
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HQPerson_$_HQA,  //分类中定义的实例方法列表
    0, //分类中定义的类方法列表
    0, //分类中定义的协议列表
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HQPerson_$_HQA,    //分类中定义的属性列表
};
static void OBJC_CATEGORY_SETUP_$_HQPerson_$_HQA(void ) {
    _OBJC_$_CATEGORY_HQPerson_$_HQA.cls = &OBJC_CLASS_$_HQPerson;
}
  • 1: 这里分类的名称显示的是"HQPerson",但是在运行时,runtime会将其修改成分类的名称.
  • 2: 在结构体中所属的父类为0,但在运行时,会调用OBJC_CATEGORY_SETUP_$_HQPerson_$_HQA静态函数,将其所属的类修改成&OBJC_CLASS_$_HQPerson.
  • 3: 分类中未定义类方法,因此类方法处为0.
  • 4: 分类中未定义协议,因此协议也为0.
  • 5: _OBJC_$_CATEGORY_INSTANCE_METHODS_HQPerson_$_HQA指定一个实例方法列表
struct _objc_method {
    struct objc_selector * _cmd;
    const char *method_type;
    void  *_imp;
};
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[3];
}
_OBJC_$_CATEGORY_INSTANCE_METHODS_HQPerson_$_HQA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    3,
    {{(struct objc_selector *)"instanceHQAMethod2", "v16@0:8", (void *)_I_HQPerson_HQA_instanceHQAMethod2},
    {(struct objc_selector *)"instanceMethod1", "v16@0:8", (void *)_I_HQPerson_HQA_instanceMethod1},
    {(struct objc_selector *)"instanceHQAMethod1", "v16@0:8", (void *)_I_HQPerson_HQA_instanceHQAMethod1}}
};
* [参数1]:_objc_method结构体的大小
* [参数2]:方法个数,分类中共有3个方法
* [参数3]:方法列表
  • 6: _OBJC_$_PROP_LIST_HQPerson_$_HQA指定一个属性列表
struct _prop_t {
    const char *name;
    const char *attributes;
};
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
}
_OBJC_$_PROP_LIST_HQPerson_$_HQA __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"catName","T@\"NSString\",&,N"}}
};
* [参数1]: _prop_t大小
* [参数2]: 属性个数,分类中定义了一个名为catName的属性
* [参数3]: 属性列表

总结:

  • 分类是对原有类进行扩展.可以访问原有类的.h文件中的属性.
  • 从源码中看到,在分类中可以定义实例方法/对象方法/属性/协议.
  • 当在分类中添加属性时,并不会为属性添加getter/setter方法以及相应的私有成员变量.如本示例中,定义了catName的属性,但在方法列表中并未看见相应的getter/setter方法,而且在category_t结构中也未查看存储成员变量的地方.注意:若需要在分类中使用分类,可以使用关联对象的方法.
  • 当原有类和分类中有相同的方法名时,当进行方法查找时会优先找到分类中方法的IMP,这是因为一个类永远只有一个类对象,分类中的方法会合并至类的方法列表中,后面会详细描述合并过程.

分类的加载

分类最重要的一个用途是,为原有类添加方法,在OC中一个类永远只有一个类对象,那分类中的方法是如何合并到原有类中的呢?
在原有类中存在两种加载方式,即懒加载与非懒加载,那分类是否也有实现+load方法和未实现+load方法的区别呢?
接下来开始进行分类方法加载的探索.

主类实现+load方法 + 分类未实现+load方法

根据类加载的原理,当主类实现+load方法时,类的初始化流程:map_images -> realizeClassWithoutSwift.类的实例方法会在methodizeClass中进行处理.因此,直接将断点停在methodizeClass中,查看当前方法列表中的数据.

method_list_t *list = ro->baseMethods();

(lldb) p list
(method_list_t *) $0 = 0x0000000100008040
(lldb) p $0->get(0).big()
(method_t::big) $1 = {
  name = "instanceHQBMethod2"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003bd0 (HQObjc`-[HQPerson(HQB) instanceHQBMethod2] at HQPerson+HQB.m:16)
}
(lldb) p $0->get(1).big()
(method_t::big) $2 = {
  name = "instanceMethod1"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003c00 (HQObjc`-[HQPerson(HQB) instanceMethod1] at HQPerson+HQB.m:20)
}
(lldb) p $0->get(2).big()
(method_t::big) $3 = {
  name = "instanceHQBMethod1"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003c30 (HQObjc`-[HQPerson(HQB) instanceHQBMethod1] at HQPerson+HQB.m:24)
}
(lldb) p $0->get(3).big()
(method_t::big) $4 = {
  name = "instanceHQAMethod2"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x00000001000039b0 (HQObjc`-[HQPerson(HQA) instanceHQAMethod2] at HQPerson+HQA.m:17)
}
(lldb) p $0->get(4).big()
(method_t::big) $5 = {
  name = "instanceMethod1"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x00000001000039e0 (HQObjc`-[HQPerson(HQA) instanceMethod1] at HQPerson+HQA.m:21)
}
(lldb) p $0->get(5).big()
(method_t::big) $6 = {
  name = "instanceHQAMethod1"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003a10 (HQObjc`-[HQPerson(HQA) instanceHQAMethod1] at HQPerson+HQA.m:25)
}
(lldb) p $0->get(6).big()
(method_t::big) $7 = {
  name = "instancePersonMethod2"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003aa0 (HQObjc`-[HQPerson instancePersonMethod2] at HQPerson.m:17)
}
(lldb) p $0->get(7).big()
(method_t::big) $8 = {
  name = "instanceMethod1"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003ad0 (HQObjc`-[HQPerson instanceMethod1] at HQPerson.m:21)
}
(lldb) p $0->get(8).big()
(method_t::big) $9 = {
  name = "instancePersonMethod1"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003b00 (HQObjc`-[HQPerson instancePersonMethod1] at HQPerson.m:25)
}
(lldb) p $0->get(9).big()
(method_t::big) $10 = {
  name = "name"
  types = 0x0000000100003f8c "@16@0:8"
  imp = 0x0000000100003b30 (HQObjc`-[HQPerson name] at HQPerson.h:18)
}
(lldb) p $0->get(10).big()
(method_t::big) $11 = {
  name = "setName:"
  types = 0x0000000100003f94 "v24@0:8@16"
  imp = 0x0000000100003b60 (HQObjc`-[HQPerson setName:] at HQPerson.h:18)
}
(lldb) p $0->get(11).big()
(method_t::big) $12 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f78 "v16@0:8"
  imp = 0x0000000100003b90 (HQObjc`-[HQPerson .cxx_destruct] at HQPerson.m:11)
}

从lldb的结果中可以看出,类的实例方法/分类HQA/分类HQB的方法,在编译时期已经合并.

主类未实现+load方法 + 分类未实现+load方法

当主类未实现+load方法时,类的初始化操作推迟至第一次方法调用时,流程:lookUpImpOrForward -> realizeClassWithoutSwift,类的实例方法会在methodizeClass中进行处理.因此,直接将断点停在methodizeClass中,查看当前方法列表中的数据.

method_list_t *list = ro->baseMethods();
(lldb) p list
(method_list_t *) $3 = 0x0000000100008040
(lldb) p $3->get(0).big()
(method_t::big) $4 = {
  name = "instanceHQBMethod2"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003be0 (HQObjc`-[HQPerson(HQB) instanceHQBMethod2] at HQPerson+HQB.m:16)
}
(lldb) p $3->get(1).big()
(method_t::big) $5 = {
  name = "instanceMethod1"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003c10 (HQObjc`-[HQPerson(HQB) instanceMethod1] at HQPerson+HQB.m:20)
}
(lldb) p $3->get(2).big()
(method_t::big) $6 = {
  name = "instanceHQBMethod1"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003c40 (HQObjc`-[HQPerson(HQB) instanceHQBMethod1] at HQPerson+HQB.m:24)
}
(lldb) p $3->get(3).big()
(method_t::big) $7 = {
  name = "instanceHQAMethod2"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x00000001000039f0 (HQObjc`-[HQPerson(HQA) instanceHQAMethod2] at HQPerson+HQA.m:17)
}
(lldb) p $3->get(4).big()
(method_t::big) $8 = {
  name = "instanceMethod1"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003a20 (HQObjc`-[HQPerson(HQA) instanceMethod1] at HQPerson+HQA.m:21)
}
(lldb) p $3->get(5).big()
(method_t::big) $9 = {
  name = "instanceHQAMethod1"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003a50 (HQObjc`-[HQPerson(HQA) instanceHQAMethod1] at HQPerson+HQA.m:25)
}
(lldb) p $3->get(6).big()
(method_t::big) $10 = {
  name = "instancePersonMethod2"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003ab0 (HQObjc`-[HQPerson instancePersonMethod2] at HQPerson.m:17)
}
(lldb) p $3->get(7).big()
(method_t::big) $11 = {
  name = "instanceMethod1"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003ae0 (HQObjc`-[HQPerson instanceMethod1] at HQPerson.m:21)
}
(lldb) p $3->get(8).big()
(method_t::big) $12 = {
  name = "instancePersonMethod1"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003b10 (HQObjc`-[HQPerson instancePersonMethod1] at HQPerson.m:25)
}
(lldb) p $3->get(9).big()
(method_t::big) $13 = {
  name = "name"
  types = 0x0000000100003f86 "@16@0:8"
  imp = 0x0000000100003b40 (HQObjc`-[HQPerson name] at HQPerson.h:18)
}
(lldb) p $3->get(10).big()
(method_t::big) $14 = {
  name = "setName:"
  types = 0x0000000100003f8e "v24@0:8@16"
  imp = 0x0000000100003b70 (HQObjc`-[HQPerson setName:] at HQPerson.h:18)
}
(lldb) p $3->get(11).big()
(method_t::big) $15 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f72 "v16@0:8"
  imp = 0x0000000100003ba0 (HQObjc`-[HQPerson .cxx_destruct] at HQPerson.m:11)
}

从lldb的结果中可以看出,类的实例方法/分类HQA/分类HQB的方法,也是在编译时期已经合并.

主类实现+load方法 + 分类实现+load方法

当主类和分类同时都实现+load方法时,主类的类初始化流程为:map_images -> realizeClassWithoutSwift.类的实例方法会在methodizeClass进行处理,接下来仍然在methodizeClass方法中查看当前方法列表中的值.

method_list_t *list = ro->baseMethods();

(lldb) p list
(method_list_t *) $0 = 0x00000001000081d0
(lldb) p $0->get(0).big()
(method_t::big) $2 = {
  name = "instancePersonMethod2"
  types = 0x0000000100003f6e "v16@0:8"
  imp = 0x0000000100003a80 (HQObjc`-[HQPerson instancePersonMethod2] at HQPerson.m:17)
}
(lldb) p $0->get(1).big()
(method_t::big) $3 = {
  name = "instanceMethod1"
  types = 0x0000000100003f6e "v16@0:8"
  imp = 0x0000000100003ab0 (HQObjc`-[HQPerson instanceMethod1] at HQPerson.m:21)
}
(lldb) p $0->get(2).big()
(method_t::big) $4 = {
  name = "instancePersonMethod1"
  types = 0x0000000100003f6e "v16@0:8"
  imp = 0x0000000100003ae0 (HQObjc`-[HQPerson instancePersonMethod1] at HQPerson.m:25)
}
(lldb) p $0->get(3).big()
(method_t::big) $5 = {
  name = "name"
  types = 0x0000000100003f82 "@16@0:8"
  imp = 0x0000000100003b10 (HQObjc`-[HQPerson name] at HQPerson.h:18)
}
(lldb) p $0->get(4).big()
(method_t::big) $6 = {
  name = "setName:"
  types = 0x0000000100003f8a "v24@0:8@16"
  imp = 0x0000000100003b40 (HQObjc`-[HQPerson setName:] at HQPerson.h:18)
}
(lldb) p $0->get(5).big()
(method_t::big) $7 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f6e "v16@0:8"
  imp = 0x0000000100003b70 (HQObjc`-[HQPerson .cxx_destruct] at HQPerson.m:11)
}
(lldb) p $0->get(6).big()
Assertion failed: (i < count), function get, file /Users/macbookpro/Desktop/hq/study/OC底层原理/V1/objc/objc4-818.2/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

从lldb的结果看,当前的方法列表中只包含HQPerson中的实例方法.而分类中的方法并未合并到当前的方法列表中.那分类的方法是何时合并的呢?接着顺着断点单步调试.在load_images时,有loadAllCategories操作,这个函数就是用来加载所有分类的.loadAllCategories源码分析请看文末.在loadAllCategories函数中通过attachLists函数,将分类的方法与类的方法进行合并.

接下来通过lldb查看经过attachLists操作前后的情况

  • attachLists操作前
(lldb) p/x cls
(Class) $0 = 0x00000001000083a0 HQPerson
(lldb) p (class_data_bits_t*)(0x00000001000083a0+0x20)
(class_data_bits_t *) $1 = 0x00000001000083c0
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100704560
(lldb) p $2->methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x00000001000081d0
      }
      arrayAndFlag = 4295000528
    }
  }
}
//当前方法列表存储着主类的方法,共6个:3个实例方法,1个setter,1个getter,1个cxx
(lldb) p $3.count()
(uint32_t) $4 = 6
(lldb) p $3.beginLists()
(const method_list_t_authed_ptr<method_list_t> *) $5 = 0x00000001005eda60
(lldb) p (method_list_t**)$5
(method_list_t **) $6 = 0x00000001005eda60
(lldb) p *$6
(method_list_t *) $7 = 0x00000001000081d0
(lldb) p $7->get(0).big()
(method_t::big) $8 = {
  name = "instanceMethod1"
  types = 0x0000000100003f6e "v16@0:8"
  imp = 0x0000000100003ab0 (HQObjc`-[HQPerson instanceMethod1] at HQPerson.m:21)
}
(lldb) p $6+1
(method_list_t **) $9 = 0x00000001005eda68
(lldb) p *$9
(method_list_t *) $10 = 0x0000000000000000
  • attachLists操作后
(lldb) p $2->methods()
(const method_array_t) $11 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100704711
      }
      arrayAndFlag = 4302325521
    }
  }
}
//此时方法列表中共有9个方法,分别是主类的6个方法,分类(HQA)的3个方法
(lldb) p $11.count()
(uint32_t) $12 = 9
(lldb) p $11.beginLists()
(const method_list_t_authed_ptr<method_list_t> *) $13 = 0x0000000100704718
(lldb) p (method_list_t**)$13
(method_list_t **) $14 = 0x0000000100704718
(lldb) p *$14
//从此处可以看出,存在方法列表中的第一个列表是存在分类(HQA)中的方法
(method_list_t *) $15 = 0x0000000100008040
(lldb) p $15->get(0).big()
(method_t::big) $16 = {
  name = "instanceMethod1"
  types = 0x0000000100003f6e "v16@0:8"
  imp = 0x00000001000039c0 (HQObjc`-[HQPerson(HQA) instanceMethod1] at HQPerson+HQA.m:21)
}
(lldb) p $14+1
(method_list_t **) $17 = 0x0000000100704720
(lldb) p *$17
//从此处可以看出,存在方法列表中的第二个列表是存在主类中的方法
(method_list_t *) $18 = 0x00000001000081d0
(lldb) p $18->get(0).big()
(method_t::big) $19 = {
  name = "instanceMethod1"
  types = 0x0000000100003f6e "v16@0:8"
  imp = 0x0000000100003ab0 (HQObjc`-[HQPerson instanceMethod1] at HQPerson.m:21)
}
(lldb) p $17+1
(method_list_t **) $20 = 0x0000000100704728
(lldb) p *$20
//此时的地址已经不像方法列表的地址
(method_list_t *) $21 = 0x0002000100605d40
(lldb) p $21->get(0).big()
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
The process has been returned to the state before expression evaluation.

总结
* 当attachLists之前,当前类的方法列表中只存储了主类的方法列表,即methods()得到的方法列表中,存储的是主类方法列表的指针.
* 当attachLists之后,当前类的方法列表中存储了分类(HQA)的方法列表和主类的方法列表,即methods()得到的方法列表中,存储了主类方法列表的指针和分类方法列表的指针.
* 当attachLists完成,在methods()得到的方法列表中,分类的方法列表指针排在主类的方法列表指针之前,因此若分类和主类有同名函数,当进行消息查找时,找到分类方法后返回分类的方法而不会继续查找主类的方法,即,分类添加与主类同名的函数后,进行方法调用时,会调用分类的方法.
* 当主类实现+load方法且分类也实现+load方法时,主类的方法列表是编译时期就确定了,但分类的方法列表不会在编译时期合并至主类的方法列表,而是以动态的通过runtime全并至主类,并将合并之后的方法列表存储在rwe中.

主类未实现+load方法 + 分类实现+load方法

主类未实现+load方法时,理论上类的初始化操作需要推迟至第一次方法调用时,但由于分类实现了+load方法,通过单步调试时发现,进入map_images -> realizeClassWithoutSwift-> methodizeClass流程,这也是主类实现了+load方法非懒加载流程.

通过堆栈信息验证,当主类未实现+load方法,分类实现+load方法时,类的加载流程是非懒加载形式,其堆栈信息如下:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001002f6965 libobjc.A.dylib`realizeClassWithoutSwift(cls=HQPerson, previously=0x0000000000000000) at objc-runtime-new.mm:2659:57
frame #1: 0x00000001002d41d6 libobjc.A.dylib`_read_images(hList=0x00007ffeefbf36d0, hCount=261, totalClasses=22057, unoptimizedTotalClasses=22057) at objc-runtime-new.mm:3835:13
frame #2: 0x00000001002d3008 libobjc.A.dylib`map_images_nolock(mhCount=261, mhPaths=0x00007ffeefbf4e00, mhdrs=0x00007ffeefbf4060) at objc-os.mm:596:9
frame #3: 0x00000001002d2b63 libobjc.A.dylib`map_images(count=261, paths=0x00007ffeefbf4e00, mhdrs=0x00007ffeefbf4060) at objc-runtime-new.mm:3165:12
frame #4: 0x0000000100019668 dyld`dyld::notifyBatchPartial(dyld_image_states, bool, char const* (*)(dyld_image_states, unsigned int, dyld_image_info const*), bool, bool) + 1749
frame #5: 0x0000000100019809 dyld`dyld::registerObjCNotifiers(void (*)(unsigned int, char const* const*, mach_header const* const*), void (*)(char const*, mach_header const*), void (*)(char const*, mach_header const*)) + 63
frame #6: 0x00007fff6f1d3ad1 libdyld.dylib`_dyld_objc_notify_register + 113
frame #7: 0x00000001002d261a libobjc.A.dylib`_objc_init at objc-os.mm:939:5
frame #8: 0x000000010044f0bc libdispatch.dylib`_os_object_init + 13
frame #9: 0x000000010045fafc libdispatch.dylib`libdispatch_init + 282
frame #10: 0x00007fff6c144791 libSystem.B.dylib`libSystem_initializer + 220
frame #11: 0x000000010002f1d3 dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 535
frame #12: 0x000000010002f5de dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
frame #13: 0x0000000100029ffb dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 493
frame #14: 0x0000000100029f66 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 344
frame #15: 0x00000001000280b4 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 188
frame #16: 0x0000000100028154 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 82
frame #17: 0x0000000100016662 dyld`dyld::initializeMainExecutable() + 129
frame #18: 0x000000010001bbba dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6667
frame #19: 0x0000000100015227 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 453
frame #20: 0x0000000100015025 dyld`_dyld_start + 37

此时通过lldb查看类的方法列表中的数据如下:

method_list_t *list = ro->baseMethods();

(lldb) p list
(method_list_t *) $0 = 0x0000000100008078
(lldb) p $0->get(0).big()
(method_t::big) $1 = {
  name = "instanceHQBMethod2"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003bd0 (HQObjc`-[HQPerson(HQB) instanceHQBMethod2] at HQPerson+HQB.m:16)
}
(lldb) p $0->get(1).big()
(method_t::big) $2 = {
  name = "instanceMethod1"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003c00 (HQObjc`-[HQPerson(HQB) instanceMethod1] at HQPerson+HQB.m:20)
}
(lldb) p $0->get(2).big()
(method_t::big) $3 = {
  name = "instanceHQBMethod1"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003c30 (HQObjc`-[HQPerson(HQB) instanceHQBMethod1] at HQPerson+HQB.m:24)
}
(lldb) p $0->get(3).big()
(method_t::big) $4 = {
  name = "instanceHQAMethod2"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x00000001000039e0 (HQObjc`-[HQPerson(HQA) instanceHQAMethod2] at HQPerson+HQA.m:17)
}
(lldb) p $0->get(4).big()
(method_t::big) $5 = {
  name = "instanceMethod1"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003a10 (HQObjc`-[HQPerson(HQA) instanceMethod1] at HQPerson+HQA.m:21)
}
(lldb) p $0->get(5).big()
(method_t::big) $6 = {
  name = "instanceHQAMethod1"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003a40 (HQObjc`-[HQPerson(HQA) instanceHQAMethod1] at HQPerson+HQA.m:25)
}
(lldb) p $0->get(6).big()
(method_t::big) $7 = {
  name = "instancePersonMethod2"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003aa0 (HQObjc`-[HQPerson instancePersonMethod2] at HQPerson.m:17)
}
(lldb) p $0->get(7).big()
(method_t::big) $8 = {
  name = "instanceMethod1"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003ad0 (HQObjc`-[HQPerson instanceMethod1] at HQPerson.m:21)
}
(lldb) p $0->get(8).big()
(method_t::big) $9 = {
  name = "instancePersonMethod1"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003b00 (HQObjc`-[HQPerson instancePersonMethod1] at HQPerson.m:25)
}
(lldb) p $0->get(9).big()
(method_t::big) $10 = {
  name = "name"
  types = 0x0000000100003f91 "@16@0:8"
  imp = 0x0000000100003b30 (HQObjc`-[HQPerson name] at HQPerson.h:18)
}
(lldb) p $0->get(10).big()
(method_t::big) $11 = {
  name = "setName:"
  types = 0x0000000100003f99 "v24@0:8@16"
  imp = 0x0000000100003b60 (HQObjc`-[HQPerson setName:] at HQPerson.h:18)
}
(lldb) p $0->get(11).big()
(method_t::big) $12 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f7d "v16@0:8"
  imp = 0x0000000100003b90 (HQObjc`-[HQPerson .cxx_destruct] at HQPerson.m:11)
}
(lldb) p $0->get(12).big()
Assertion failed: (i < count), function get, file /Users/macbookpro/Desktop/hq/study/OC底层原理/V1/objc/objc4-818.2/runtime/objc-runtime-new.h, line 624.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

从lldb的结果看,类的实例方法/分类HQA/分类HQB的方法,也是在编译时期已经合并.

总结

  • 类的初始化分为懒加载(实现+load方法)非懒加载(未实现+load方法)两种形式.

  • 分类的方法合并至主类的方法列表分为编译时合并通过runtime动态合并.

  • 当类与分类是否实现+load时,方法列表的合并有4种情况:

    • 主类实现+load方法 + 分类未实现+load方法时,主类是非懒加载的,分类的方法是在编译时期已经与主类的方法进行合并.
    • 主类未实现+load方法 + 分类未实现+load方法时,主类是懒加载的,分类的方法也是在编译时期已经与主类的方法进行合并.
    • 主类实现+load方法 + 分类实现+load方法时,主类非懒加载的,分类的方法是通过runtime动态的与主类方法进行合并.
    • 主类未实现+load方法 + 分类实现+load方法时,主类转换成非懒加载形式,分类的方法也是在编译时期与主类的方法进行合并.
  • 分类的方法列表在编译时期合并时,当使用methods()方法获取类的方法列表时,返回的结果中只有一个method_list_t指针,该指针指向的方法method_t全部进行SEL大小排序.
    如:HQPerson(HQA) 方法1 < HQPerson(HQB) 方法1< HQPerson 方法1 < HQPerson(HQA) 方法2 < HQPerson(HQB) 方法2<HQPerson 方法2.

  • 分类的方法列表通过runtime合并时,当使用methods()方法获取类的方法列表时,返回的结果中有三个method_list_t指针,每个method_list_t指针指向的方法method_t进行独立的SEL大小排序.
    如:
    HQPerson(HQA) 方法1 < HQPerson(HQA) 方法2 ;
    HQPerson(HQB) 方法1 < HQPerson(HQB) 方法2 ;
    HQPerson 方法1 < < HQPerson 方法2 .
    而方法列表是按编译的顺序的逆序进行排列,先编译的在列表的尾部,后编译的在列表的前面.
    如:
    methods()->beginLists()[0] = HQPerson(HQB)的method_list_t
    methods()->beginLists()[1] = HQPerson(HQA) 的method_list_t
    methods()->beginLists()[2] = HQPerson的method_list_t

分类中通过关联对象的方式添加属性

首先,验证是否能在分类中定义catName的属性.
在分类中添加catName的属性,在main函数中对其进行赋值.在编译的时候并未报错,这说明,是可以在分类中定义属性的.

其次,验证是否能在main函数中对catName属性进行赋值.
在main函数中调用 person.catName = @"123"; 结果执行崩溃,错误信息如下:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HQPerson setCatName:]: unrecognized selector sent to instance 0x100645d60'

从错误提示中可知,是未找到setCatName的实例方法.这也验证了,分类中定义属性时,不会为该属性添加getter/setter方法.
既然分类没有自动添加getter/setter方法,那是不是可以手动添加getter/setter方法呢?答案是可以的,因为OC是动态语言,方法真正的实现是通过runtime完成的,既然这样那手动添加试试.

在HQPerson+HQA.m中添加catName的getter/setter方法

//运行时实现setter方法
- (void)setCatName:(NSString *)catName{
    objc_setAssociatedObject(self, "catName", catName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

//运行时实现getter方法
- (NSString *)catName{
    return objc_getAssociatedObject(self, "catName");
}

增加关联对象后效果如下:

person.catName = @"123";    //执行setter成功
NSLog(@"catName:%@",person.catName);    //执行getter成功,取出的值为@"123"

经过验证,在分类中可以添加属性,在分类的结构体中,使用instanceProperties指向了分类的属性列表.但分类不会为属性添加getter/setter方法和私有成员变量.如果需要使用分类中的属性,则必须手动添加getter/setter方法,可通过关联对象的方法或者使用@dynamic方法.

loadAllCategories函数源码分析

  • 1:查看loadAllCategories源码如下:
static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);

    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        load_categories_nolock(hi);
    }
}
  • 2:进入load_categories_nolock函数
static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    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) {
                // Category's target class is missing (probably weak-linked).
                // Ignore the category.
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category.
            if (cls->isStubClass()) {
                // Stub classes are never realized. Stub classes
                // don't know their metaclass until they're
                // initialized, so we have to add categories with
                // class methods or properties to the stub itself.
                // methodizeClass() will find them and add them to
                // the metaclass as appropriate.
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // First, register the category with its target class.
                // Then, rebuild the class's method lists (etc) if
                // the class is realized.
                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());
                    }
                }
            }
        }
    };

    //处理__objc_catlist记录的分类
    processCatlist(hi->catlist(&count));
    //处理__objc_catlist2记录的分类
    processCatlist(hi->catlist2(&count));
}
  • 2.1:通过_getObjc2CategoryList函数,获取MachO文件中分类的个数以及分类列表的地址.注意:当分类未实现+load方法时,此列表中不会存储分类的信息.
processCatlist(hi->catlist(&count));
category_t * const *header_info::catlist(size_t *outCount) const
{
    // This field is new, so temporarily be resilient to the shared cache
    // not generating it
    if (isPreoptimized() && hasPreoptimizedSectionLookups()) {
      *outCount = catlist_count;
      category_t * const *list = (category_t * const *)(((intptr_t)&catlist_offset) + catlist_offset);
      return list;
    }
    //从MachO文件中获取__objc_catlist段数据,__objc_catlist段中数据表示MachO中记录的分类
    return _getObjc2CategoryList(mhdr(), outCount);
}

GETSECT(_getObjc2CategoryList, category_t * const, "__objc_catlist");//记录了所使用的分类
  • 2.2:遍历分类列表,依次对分类列表中的分类进行处理attachCategories.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count, int flags)
{
    //无关代码省略
    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,并将类的基本方法赋值给rwe
    auto rwe = cls->data()->extAllocIfNeeded();
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];
        //获取分类中的方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __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) {
        //对分类方法列表中的方法进行排序,排序方法按SEL的由小到大顺序
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        //将排序后的分类方法合并至rwe中.此时rwe是一个二维数组,并且,二维数组的是按逆序插入的,即在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);
}
  • 2.3:由于类的ro编译时已经确定,并且不能在运行时进行修改,因此在attachCategories函数中,开辟rwe,并将ro->baseMethods通过rwe->methods.attachLists函数赋值给rwe.注意:此时rwe是一个一维数组
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();

    //开辟rwe的内存空间
    auto rwe = objc::zalloc<class_rw_ext_t>();

    rwe->version = (ro->flags & RO_META) ? 7 : 0;

    method_list_t *list = ro->baseMethods();
    if (list) {
        if (deepCopy) list = list->duplicate();
        //将原有类的方法列表赋值给rwe
        rwe->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        //将原有类的属性列表赋值给rwe
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        //将原有类的协议列表赋值给rwe
        rwe->protocols.attachLists(&protolist, 1);
    }
    set_ro_or_rwe(rwe, ro);
    return rwe;
}
  • 2.4:遍历分类的方法列表,将分类的方法列表通过rwe->methods.attachLists方法附着到主类的rwe中.
void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        //获取旧的lists个数
        uint32_t oldCount = array()->count;
        //加上当前需要添加的list的个数,得到新的lists的大小
        uint32_t newCount = oldCount + addedCount;
        //开辟newCount个大小的数组,用于存储当前所有的list
        array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
        newArray->count = newCount;
        array()->count = newCount;

        for (int i = oldCount - 1; i >= 0; i--)
            //将旧的lists中的数据按原顺序添加至新lists的尾部
            newArray->lists[i + addedCount] = array()->lists[i];
        for (unsigned i = 0; i < addedCount; i++)
            //将新lists中的数据从头开始添加至新lists中
            newArray->lists[i] = addedLists[i];
        free(array());
        setArray(newArray);
        validate();
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        //由于此时lists数组中为空,此时直接将addedLists[0]赋值给lists
        list = addedLists[0];
        validate();
    } 
    else {
        // 1 list -> many lists
        Ptr<List> oldList = list;
        //得到当前list的数量
        uint32_t oldCount = oldList ? 1 : 0;
        //计算容量和 = 旧list个数+新lists的个数
        uint32_t newCount = oldCount + addedCount;
        //开辟一个容量和大小的集合,类型是 array_t,即创建一个数组,放到array中,通过array()获取
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        //设置数组的大小
        array()->count = newCount;
        //将旧的list放入到新数组的末尾
        if (oldList) array()->lists[addedCount] = oldList;
        // memcpy(开始位置,放什么,放多大) 是内存平移,从数组起始位置存入新的list
        //其中array()->lists 表示首位元素位置
        for (unsigned i = 0; i < addedCount; i++)
            array()->lists[i] = addedLists[i];
        validate();
    }
}

loadAllCategories函数方法合并总结

  • 开辟rwe,并将原有类的方法列表赋值给rwe.此时rwe是一个指针,指向原有类的方法列表.
  • 遍历每个分类,获取分类中的方法列表,注意:一个类最多有64个分类
    • 第一次获取到分类的方法列表时,此时rwe中已经有方法列表,创建一个大小为2的数组.将旧的方法列表放到数组的末尾,将新的方法列表从数组的起始位置存入数组中.此时rwe是一个数组.
    • rwe是以数组的形式存放方法列表时,则根据当前分类中方法列表的个数,扩大rwe的数组,将rwe原数组中内容放置新数组的末尾,将新的方法列表从新数组的起始位置存入数组中.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容