在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)
- 第2次进入打印分类
CB
信息:
(lldb) p cats_list
(const locstamped_category_t *) $20 = 0x00007ff7bfefd648
(lldb) p *$20
(const locstamped_category_t) $21 = {
cat = 0x0000000100008360
hi = 0x0000600002611860
}
(lldb) p $21.cat
(category_t *const) $22 = 0x0000000100008360
(lldb) p *$22
(category_t) $23 = {
name = 0x0000000100003f6d "CB"
cls = 0x00000001000084b8
instanceMethods = {
ptr = 0x00000001000082d8
}
classMethods = {
ptr = 0x0000000100008328
}
protocols = nil
instanceProperties = nil
_classProperties = nil
}
(lldb)
- 第三次进入打印分类
CC
信息:
(lldb) p cats_list
(const locstamped_category_t *) $24 = 0x00007ff7bfefd648
(lldb) p *$24
(const locstamped_category_t) $25 = {
cat = 0x0000000100008428
hi = 0x0000600002611860
}
(lldb) p $25.cat
(category_t *const) $26 = 0x0000000100008428
(lldb) p *$26
(category_t) $27 = {
name = 0x0000000100003f70 "CC"
cls = 0x00000001000084b8
instanceMethods = {
ptr = 0x00000001000083a0
}
classMethods = {
ptr = 0x0000000100008408
}
protocols = nil
instanceProperties = nil
_classProperties = nil
}
(lldb)
- 加载完三个分类,回到
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 |