在dyld4流程下中,我们探索和分析了map_images
、load_images
这两个函数;但是没有对类的加载做出详细解释,本文就探讨下类的加载。
一、类的加载
在前一篇文章中read_images函数中,我们说到非懒加载类的加载是通过调用realizeClassWithoutSwift
来实现的,那我们就首先看下这个函数源码。
-
结论:
realizeClassWithoutSwift
函数的主要作用:-
①. 读取
data
数据,并设置ro
、rw
; -
②. 递归调用
realizeClassWithoutSwift
完善继承链; -
③. 处理方法、属性、协议列表等。
-
读取data
数据并设置ro
、rw
通过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
- 然后在
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
使用。
-
在判断条件中打上断点调试,运行后查看
ro
情况: -
可以看到
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)
- 在这里就可以看到,一开始
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
}
- 代码走过
rw->set_ro(ro)
以及cls->setData(rw)
之后,我们打印cls
中ro()
,此时就有了数据,
(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)
- 在之前的文章OC类的探索-bits当中,
- ①. 当类第一次从磁盘加载到内存时,会在类的
bits
中获取到ro
-
②. 然后由
ro
来设置rw
的数据,这里是通过rw->set_ro(ro)
来设置rw
中的ro
值。 -
③. 而
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"
- 然后通过如下代码,对当前
cls
设置父类元类
// 将父类和元类赋值给当前类,分别是isa和父类的对应值
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
- 通过如下代码,来对父类中的子类列表添加子类
//双向链表指向关系,父类中可以找到子类,子类中也可以找到父类
//通过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都是没有值的),所以attachLists
和attachToClass
这俩函数在下面中说明。
-
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();
}
}
-
调试图:
-
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_init
的map_images
时去加载。
-
二、分类
平时在开发的时候会经常使用分类来添加方法、协议、属性,但在添加属性的时候属性是不会自动生成成员变量的,这时候我们就需要关联对象来动态存储属性值。
分类的本质
- 首先要知道分类的本质是什么,我们定义一个分类。
- 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
- 在终端里使用
clang -rewrite-objc CJPerson+Category.m -o CJPerson+Category.cpp
重写将上面的OC
代码成c++
代码。
-
图:
-
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;
};
- 可以看到其根本的实现是
_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;
}
}
- 那么
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,
};
- 看到该结构体对应
category_t
结构体六个值,但是第二个值cls
的值为0
,这是为什么呢?
这是因为在编译阶段,CJPerson+Category
这个分类还没有与CJPerson
进行关联。因此cls
为0
。
- ①.
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}}
};
-
总结:
可以看到这里就两个实例方法
test
和category_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
这里就是类方法列表,包含编译后的load
和categoty_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
类型的属性列表,只表示分类中定义的属性(只有定义)
-
-
分类中没有成员变量,也没有
set
、get
方法,分类中的属性都是通过关联对象来实现的。
分类的加载
其实分类主要加载方法就是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判断条件去判断是否加载分类?可是
didInitialAttachCategories
在load_images
后才会被赋值为true
,而这个流程是在map_images
中的,dyld4
是先map_images
再load_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
通过cls
与flags
参数区分类和元类。cats_count
参数写死的是1
。locstamped_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
,当mcount
为64
的时候,重新开始计数。也就是说当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
,如果之前创建过直接返回 - ②. 将
ro
中methods
数据拷贝到rwe
中(这里没有深拷贝,通过extAllocIfNeeded
函数调用的都是浅拷贝)。 - ③. 链接属性和协议。
- ④. 设置
rwe
,rwe->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
数据(加载过多个分类),那么就是多对多的流程。
-
三、探究类与分类加载的流程
通过对类的加载与分类的加载源码分析,我们可以根据是否懒加载的情况?大致将类与分类的加载分为以下八种情况:
类+分类 | 单个分类 | 多个分类 | 单个分类+实现load | 多个分类+实现load |
---|---|---|---|---|
类 | 懒加载类+懒加载分类 | 懒加载类+多个懒加载分类 | 懒加载类+非懒加载分类 | 懒加载类+多个非懒加载分类 |
类+实现load | 非懒加载类+懒加载分类 | 非懒加载类+多个懒加载分类 | 非懒加载类+非懒加载分类 | 非懒加载类+多个非懒加载分类 |
- 准备工作:
- 在objc4-886.9
realizeClassWithoutSwift
函数中添加调试代码并打上断点,方便在自定义的主类加载过程暂停:
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__);
}
...
...
}
- 在分类加载函数
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__);
}
...
...
}
- 自定义的类
CJPerson
与多个分类CJPerson+CA
、CJPerson+CB
、CJPerson+CC
。主类的instanceMethods
实例方法有7
个,CJPerson+CA
与CJPerson+CB
有3
个,CJPerson+CC
有4
个。
- 主类源码:
// 主类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
的引入,屏蔽其他两个分类。
- 在
Xcode
的Compile Source
里设置。
- 图:
- 将主类
CJPerson
与分类CJPerson+CA
里的类方法+load
全部屏蔽起来。
- 图:
- 运行可调试源码,会卡在
CJPerson
第一次进行消息慢速查找时调用主类加载函数realizeClassWithoutSwift
。
- 图:
- 编译运行程序,程序卡在了断点,此时
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
函数之前,分类中的方法已经被加载到主类的方法列表中了,并且是加载到了ro
的baseMethodList
中,类的rw_ext
是nil
值,清空输出,过掉断点,程序并不会执行attachCategories
函数。
总结:
-
①. 当主类及其分类都为懒加载模式时,并不会调用
attachCategories
函数将分类中的方法附着到主类中,而是编译器完成了这部分操作,并且主类的方法列表是一维的,顺序是分类中的方法在主类方法前。 -
②. 分类数据加载后主类方法类别内存结构示意图如下所示:
多个懒加载分类
如果主类的分类很多,懒加载主类与多个懒加载分类又是如何处理的呢?
- 在
Xcode
的Compile Source
里设置。
- 如图:
- 将其他两个分类
CJPerson+CB
与CJPerson+CC
里的类方法+load
全部屏蔽起来。
- 图:
- 运行可调试源码,跟单个懒加载分类一样都会停在
CJPerson
第一次进行消息慢速查找时调用主类加载函数realizeClassWithoutSwift
。
- 调试图:
- 编译运行程序,会发现依然执行的是主类的懒加载流程,打印主类中
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)
总结:
-
①. 当主类都为懒加载模式时,无论懒加载分类有多少个都是第一次进行消息慢速查找时,是编译器完成了分类中的方法附着到主类中,并且主类的方法列表是一维的,顺序是分类中的方法在主类方法前。
-
②. 可以发现,主类方法列表中的方法顺序是与分类的编译顺序有关的,每编译一个分类时,会将其所有的方法插入到其主类方法列表的最前面。示意图如下所示:
非懒加载主类以及懒加载分类
单个懒加载分类
主类中开启+load
类方法,只留一个CJPerson
分类CA
。
- 首先在
Xcode
的Compile Source
里设置成单个分类的模式,这个跟上面一样。
- 设置图:
- 将主类
CJPerson
设置成非懒加载类
- 图:
- 编译运行代码,程序执行到断点,查看一下函数调用栈。
-
如图所示:
-
结论:
此时非懒加载类的路径为:
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
文件时的优化工作。
多个懒加载分类
- 重新把剩余的两个分类加回
Xcode
的Complie Source
里,为了验证之前说尾插入分类而更改它们顺序下,按照CC
、CA
、CB
的先后编译顺序。
- 如下图所示:
- 主类为非懒加载,分类
CC
、CA
、CB
都是懒加载 - 编译运行程序,程序执行到断点,查看函数调用栈。
-
如下图所示:
-
结论:
非懒加载类的路径为:
map_images
->map_images_nolock
->_read_images
->realizeClassWithoutSwift
- 并打印输出
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
时开始加载的,无论有单个还是多个懒加载分类都是存放类的ro
的baseMethods
里的一维数组。 -
②. 编译的先后顺序会影响分类的存储顺序。
非懒加载主类以及非懒加载分类
单个非懒加载分类
- 主类中保持开启
load
类方法,只留一个分类CJPerson+CA
,并在分类中开启+load
类方法,在main
函数中调用分类中的实例方法,并打上如下断点。
- 图:
- 编译运行代码,程序执行到断点,查看一下函数调用栈。
-
如下图所示:
-
结论:
非懒加载主类的调用路径为:
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
,但是非懒加载分类与懒加载分类是不同的,是没有经过编译器优化的。
- 过掉断点,继续执行程序,发现执行到了分类加载函数
attachCategories
中的断点。
-
如下图所示:
-
结论:
可以发现分类的调用路径是
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
- 继续查看
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
类方法后,就会在ObjC
的load_images
函数调用时加载分类,跳过断点,来到attachCategories
函数,此时CJPerson
类中的rwe
就被初始化了,并且会调用attachLists
函数将CJPerson+CA
分类中的方法附着到主类的方法列表中 -
②. 当分类中方法列表为空并且附着的方法列表数量为1时(
list
为nil
并且addedCount
),直接将此方法列表的地址赋值到rwe
的methods
(C++
类method_array_t
)的成员变量list
(指针类型)中,这个list
的值只是一个存储method_t
的一维数组的地址。
-
- 打断点进入到
attachLists
,这个方法会将分类的method
添加到类的rwe
的methods
里。离开这个断点回到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
会发现分类也已经添加完毕了,CJPerson
的rwe
的methods
的方法数count
从7
变成了10
。 -
②. 通过
x/4gx 0x0000600000200780
可以知道list_array_tt
内存分布情况。首个值0x0000000000000002
是保存的method_list_t
的值2
,代表一个分类与主类。0x00000001000081e8
是分类CJPerson+CA
的实例方法列表地址,0x0000000100008080
是主类CJPerson
的ro
的baseMethods
的地址。
-
总结:
- 懒加载分类是通过
attachCategories
添加的,而且没有编译器优化,不会保存在ro
里,只能通过建立rwe
后再一个个分类赋值。
多个非懒加载分类
- 在工程中添加
CB
、CC
,按照CA
、CB
、CC
顺序编译。
- 如下图所示:
- 让分类
CJPerson+CC
保持为懒加载分类,其他分类为非懒加载分类,可以验证一下不同情况。
- 如图:
- 编译运行程序,执行到如下断点,查看函数调用栈。
-
如下所示:
-
结论:
在无论分类是否是懒加载分类的情况下,非懒加载类的加载方式还是
map_images
->map_images_nolock
->_read_images
->realizeClassWithoutSwift
。
- 打印类中的方法列表中的数据,可以发现的是编译器并未将分类中的数据附着到主类中去。
- 打印信息:
(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
- 过掉断点,程序卡在了
attachCategories函数
函数中,此时打印mCount
的值1
,也就是当前加载的分类的数量为1。而这个函数会调用3
次,正好对应3
个分类:CA
、CB
、CC
。
-
如下图所示:
-
第一次进入打印分类
CA
信息:
(lldb) p cats_list
(const locstamped_category_t *) $16 = 0x00007ff7bfefd648
(lldb) p cats_list->cat
(category_t *const) $17 = 0x0000000100008298
(lldb) p *$17
(category_t) $18 = {
name = 0x0000000100003f6a "CA"
cls = 0x00000001000084b8
instanceMethods = {
ptr = 0x00000001000081e8
}
classMethods = {
ptr = 0x0000000100008238
}
protocols = nil
instanceProperties = 0x0000000100008270
_classProperties = nil
}
(lldb)
- 第二次进入打印分类
CB
信息:
(lldb) p cats_list
(const locstamped_category_t *) $20 = 0x00007ff7bfefd648
(lldb) p *$20
(const locstamped_category_t) $21 = {
cat = 0x0000000100008360
hi = 0x0000600002611860
}
(lldb) p $21.cat
(category_t *const) $22 = 0x0000000100008360
(lldb) p *$22
(category_t) $23 = {
name = 0x0000000100003f6d "CB"
cls = 0x00000001000084b8
instanceMethods = {
ptr = 0x00000001000082d8
}
classMethods = {
ptr = 0x0000000100008328
}
protocols = nil
instanceProperties = nil
_classProperties = nil
}
(lldb)
- 第三次进入打印分类
CC
信息:
(lldb) p cats_list
(const locstamped_category_t *) $24 = 0x00007ff7bfefd648
(lldb) p *$24
(const locstamped_category_t) $25 = {
cat = 0x0000000100008428
hi = 0x0000600002611860
}
(lldb) p $25.cat
(category_t *const) $26 = 0x0000000100008428
(lldb) p *$26
(category_t) $27 = {
name = 0x0000000100003f70 "CC"
cls = 0x00000001000084b8
instanceMethods = {
ptr = 0x00000001000083a0
}
classMethods = {
ptr = 0x0000000100008408
}
protocols = nil
instanceProperties = nil
_classProperties = nil
}
(lldb)
- 加载完三个分类,回到
main
函数,此时再打印CJPerson
的rwe
的情况,可以知道分类方法的排列情况。
- 打印类的
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
一个接一个加载。 -
所有分类数据加载完毕之后,打印一下这个方法列表数组的地址以及其中最后一个方法列表的数据,如下图所示:
懒加载主类以及非懒加载分类
单个非懒加载分类
- 将主类
CJPerson
屏蔽+load
类方法,变回懒加载主类;而分类只保留编译一个CJPerson+CA
非懒加载分类。
- 如图:
- 编译执行程序,程序卡在断点,查看函数调用栈。
-
如图所示:
-
结论:
此时,主类
CJPerson
虽然是懒加载类,但是没有通过消息慢速查找时加载主类数据,而是跟非懒加载类时一样。主要原因是懒加载类受非懒加载分类影响变成了非懒加载类。
- 打印主类中
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
;跟非懒加载类+懒加载分类一样。
多个非懒加载分类
- 在工程中添加
CB
、CC
类,按照CC
、CB
、CA
的编译顺序。
- 如下图所示:
- 并且只在
CA
、CB
中开启+load
类方法为非懒加载分类。
- 图:
- 运行程序,程序执行到断点,查看函数调动栈。
-
如下所示:
-
结论:
-
①. 在懒加载类情况下,多个非懒加载分类跟单个是不同的。不再是
map_images
时,而是load_images
时才加载主类数据。 -
②. 主类加载调用流程:
load_images
->prepare_load_methods
->realizeClassWithoutSwift
-
- 打印类中的方法列表数据。
- 如下图所示:
(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
-
结论:
这种情况,编译器也没有将分类数据附着到主类中。
- 过掉断点,程序执行到了
attachToClass
函数中。
- 如下图所示:
- 过掉断点,程序执行到
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
一次性将三个分类按照编译顺序加入类中,正好对应了分类的加载第三条路径。
- 离开这个断点回到
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
时进行,而分类刚好通过分类的加载的第三条路径一次性加载完。 -
根据以上的探究数据,类的方法列表数组结构图如下:
结论:
根据以上探究,实际上分类的加载共有以下的5种情况:
- 当主类为懒加载类,如果所有分类也为懒加载分类,则会在类第一次发送消息时加载类数据,并且此时所有分类数据已经附着到了主类中,主类以及分类的方法列表、属性列表协议列表都是以一级指针的方式存储在
ro
中。
- 主类与分类的函数调用栈:
objc_msgSend
->lookupImpOrForward
->realizeClassWithoutSwift
- 当主类为懒加载类,但只有一个非懒加载分类时,这时就会以非懒加载的形式加载主类,并且此时所有分类数据已经附着到了主类中,主类以及分类的方法列表、属性列表协议列表都是以一级指针的方式存储在
ro
中。
- 主类与分类的函数调用栈:
map_images
->map_images_nolock
->_read_images
->realizeClassWithoutSwift
- 当主类为懒加载类,但有多个非懒加载的分类时,这时就会以非懒加载的形式加载主类,并且会调用
attachCategories
函数将所有分类数据一次性全部附着到主类中,主类以及分类的方法列表、属性列表以及协议列表都是以二级指针的方式存储在rwe
中。
- 主类与分类函数调用栈:
load_images
->prepare_load_methods
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
->attachCategories
- 当主类为非懒加载类,所有分类均为懒加载类时,这种情况与
2
中一致,主类以及分类的方法列表、属性列表以及协议列表都是以一级指针的方式存储在ro
中。
- 主类与分类的函数调用栈:
map_images
->map_images_nolock
->_read_images
->realizeClassWithoutSwift
- 当主类为非懒加载类,分类中存在至少一个非懒加载类时,除了会调用
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 |