类的懒加载和非懒加载
在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原数组中内容放置新数组的末尾,将新的方法列表从新数组的起始位置存入数组中.
-
网友评论