类加载原理(中)
接续上一篇文章类加载原理(上)的内容我们继续探索。上一篇我们一直在寻找关于类Class
的一些处理比如加载ro\rw
。所以我们在_read_images
方法里把代码根据不同功能区分了十个部分。并且根据我们的目的利用特殊打印排除的方法查找了几个。下面我们继续查找关于类的代码部分。
类的加载处理:
非懒加载类加载处理流程
我们同样利用特殊打印排除的方法来看看我们的类ZYPerson
是否会进入到这里。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
/*
* 省略上面的代码
*/
// Category discovery MUST BE Late to avoid potential races
// when other threads call the new category code before
// this thread finishes its fixups.
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
/* *********************尝试打印看看我们自己的类能不能进来*****************/
const char *mangledName = cls->nonlazyMangledName();
//定义自己的类名
const char *ZYPersonName = "ZYPerson";
//比较自己的类名和读取的是否一致一致就进入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
printf("Realize non-lazy classes - ZY 我们需要跟踪的信息: %s - - %s\n",__func__,mangledName);
}
/* *********************尝试打印看看我们自己的类能不能进来*****************/
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
// fixme also disallow relocatable classes
// We can't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
/*
* 省略下面的代码
*/
}
我们尝试跑起了我们的代码,发现并没有直接进来我们的打印打码,并且我尝试利用断点来跟踪也发现并没有进这个方法。但是这个方法明明是对类的加载的一些处理呀(看注释)最后我发现注释里有解释那就是这段代码是对非懒加载
类的处理要走这里就先要使我们的类成为非懒加载
的类,for +load methods and static instances
所以我在我的ZYPerson
类里实现了一下+ (void *)load{}
方法使之成为非懒加载
类。然后来跑代码,诶?发现真的进来了。如下:
ZYPerson.h
:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZYPerson : NSObject
- (void)zyEatSugar;
+ (void)sayHappy;
- (void)zySay1;
- (void)zySay2;
- (void)zySay3;
- (void)zyShowTime;
@end
NS_ASSUME_NONNULL_END
ZYPerson.m
:
#import "ZYPerson.h"
@implementation ZYPerson
+(void)load{
}
- (void)zyEatSugar
{
NSLog(@"%s",__func__);
}
+ (void)sayHappy
{
NSLog(@"%s",__func__);
}
- (void)zySay1
{
NSLog(@"%s",__func__);
}
- (void)zySay2
{
NSLog(@"%s",__func__);
}
- (void)zySay3
{
NSLog(@"%s",__func__);
}
- (void)zyShowTime
{
NSLog(@"%s",__func__);
}
main.m
:
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *person = [ZYPerson alloc];
//调用ZYPerson 的zyEatSugar 方法 ,该方法未实现
[person zyEatSugar];
}
return 0;
}
打印结果:
Realize non-lazy classes - ZY 我们需要跟踪的信息: _read_images - - ZYPerson
KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
2021-08-03 14:37:38.175895+0800 KCObjcBuild[25482:4921796] -[ZYPerson zyEatSugar]
Program ended with exit code: 0
发现是走了我们的打印的。所以我们再次回到这段代码。我们发现这段代码其实就两个方法是我们需要关注的其他的都是一些判断和数据处理。一个是addClassTableEntry(cls);
这个方法我们在上一篇文章——类加载原理(上)中的readClass
方法里去探索过,发现只是对类的名字/mangledName
和地址
以HashMap
的形式添加进表
的动作。通过这个方法我们只能把类名和地址关联起来,但是它怎么实现的还没看到。所以我们看另一个方法:realizeClassWithoutSwift(cls, nil);
1,realizeClassWithoutSwift(cls, nil);
:
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
这个方法我们先在开头加入我们的特殊打印打码,OB
一下,看看是否会进来,若是进来,并且我们看到这里有ro
的出现。我们就查看下下面的ro
是否存在。
从上面的操作截图来看,ZYPerson
确实进来了这个方法,但是直接打印ro
里的方法列表却打印不出来。而从上面我们贴出来的代码看我们的ZYPerson
里是有方法的。我们在main
函数里还调用了。所以到这里我们的方法还没有加载进来。我们接着往下找。
我们在下面的代码继续添加断点来跟踪下如下图:
4.png我们发现ZYPerson
走了eles
而我们在eles
里看到了我们熟悉并且一直寻找的字眼ro
、rw
。我们在上面看到这里的ro
是从cls->data()
里获取的。而这里是又利用ro
进行处理赋值给我rw
。我们就回顾去看看这个ro到底怎么获取的。
auto ro = (const class_ro_t *)cls->data();
class_rw_t *data() const {
return bits.data();
}
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
看上面我们可以知道原来
ro
是直接读取出来并且rw
应该是包含ro
的数据格式的,因为他是以rw
格式强转出来的。
在上面的
eles
代码我们还看到了将读取出来的ro
直接利用set
方法set
进了一个新创建的rw
里。然后判断是否是元类来设置rw
的flags
。最后将rw
利用set
方法set
进了class
的data()``里。这就是类加载的时候
ro、
rw`的处理。
我们接着往下看:
下面就是对类的cache做初始化处理:
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
然后就是获取到本类的父类和元类:
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
然后就是判断是否是符合SUPPORT_NONPOINTER_ISA
如果符合就走
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
在这里判断是否是元类,分别对类做处理。然后就到了下面的代码:
// Update superclass and metaclass in case of remapping
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
在这里就根据前面获取的
父类
、元类
然后把本类
和元类
以及父类
都做了相应的关联
处理。这里就是我们常用到的isa走位图
的实现和继承链
的实现了。
最后我们看到还剩一行代码:
// Attach categories
methodizeClass(cls, previously);
return cls;
2,methodizeClass
:
在这行代码我们看名称是对方法的处理。我们刚在前面利用ro打印方法列表的时候并未打印出来。因为那个时候的类还只有一个名称和一个地址关联,并没有方法ro、rw的处理。但是到这行代码我们不禁想,经过上面的对类的一些列处理,这里是否有了方法呢?下面我们进入这个方法并且利用我们的特殊打印来打印一下
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
/* *********************尝试打印看看我们自己的类能不能进来*****************/
const char *mangledName = cls->nonlazyMangledName();
//定义自己的类名
const char *ZYPersonName = "ZYPerson";
//定义变量来存储本次进来的是否是元类
auto zy_ro = (const class_ro_t *)cls->data();
auto zy_isMeta = zy_ro->flags & RO_META;
//比较自己的类名和读取的是否一致一致就进入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
if (!zy_isMeta) {//判断不是元类就打印
printf("realizeClassWithoutSwift---2222 -- ZY 我们需要跟踪的信息: %s - - %s\n",__func__,mangledName);
}
}
/* *********************尝试打印看看我们自己的类能不能进来*****************/
/*
* 省略下面的代码
*/
我们排除元类后就直接打印断点打印代码,然后去lldb
调试查看是否有方法。
Realize non-lazy classes - ZY 我们需要跟踪的信息: _read_images - - ZYPerson
realizeClassWithoutSwift - ZY 我们需要跟踪的信息: realizeClassWithoutSwift - - ZYPerson
realizeClassWithoutSwift - ZY 我们需要跟踪的信息: realizeClassWithoutSwift - - ZYPerson
(lldb) p ro
(const class_ro_t *) $0 = 0x00000001000081c8
(lldb) p *40
error: <user expression 1>:1:1: indirection requires pointer operand ('int' invalid)
*40
^~~
(lldb) p *$0
(const class_ro_t) $1 = {
flags = 0
instanceStart = 8
instanceSize = 40
reserved = 0
= {
ivarLayout = 0x0000000000000000
nonMetaclass = nil
}
name = {
std::__1::atomic<const char *> = "ZYPerson" {
Value = 0x0000000100003f07 "ZYPerson"
}
}
baseMethodList = 0x0000000100008210
baseProtocols = nil
ivars = 0x0000000100008290
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100008318
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $1->baseMethodList
(void *const) $2 = 0x0000000100008210
Fix-it applied, fixed expression was:
$1.baseMethodList
(lldb) p *$2
(lldb) p *$2
(lldb)
从上面的lldb
调试发现到这个方法开始ro
里都没有方法列表。所以我们接着往下看。
在这个方法里我们打印的下方有对方法的处理
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
这里直接去获取baseMethods
如果不为空就去做一些预备的处理进入方法prepareMethodLists
。所以我们跟踪到这个处理方法看看
3,prepareMethodLists
:
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle, const char *why)
{
runtimeLock.assertLocked();
if (addedCount == 0) return;
// There exist RR/AWZ/Core special cases for some class's base methods.
// But this code should never need to scan base methods for RR/AWZ/Core:
// default RR/AWZ/Core cannot be set before setInitialized().
// Therefore we need not handle any special cases here.
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
}
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.
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*/);
}
}
// If the class is initialized, then scan for method implementations
// tracked by the class's flags. If it's not initialized yet,
// then objc_class::setInitialized() will take care of it.
if (cls->isInitialized()) {
objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
}
}
在这个方法我们看到前面有一些判断和处理 但是重点是在于中间的for循环
。我们在for循环
里打个断点然后进行lldb
调试看看到底是怎么处理和进入的。如图5
经过上面断点调试和输出我们发现确实进入了这个for循环并且进入了方法
fixupMethodList
进行了方法修复。我们就跟踪到这个方法去看看他的实现:
4,fixupMethodList
:
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
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));
}
}
// Sort by selector address.
// Don't try to sort small lists, as they're immutable.
// Don't try to sort big lists of nonstandard size, as stable_sort
// won't copy the entries properly.
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
方法分析:
第一步:是进行了for循环
的遍历去获取到了每一个方法的名字然后利用注册方法sel_registerNameNoLock
进行了名字sel
绑定注册最后利用set
方法set
进了method
。
第二步:进行了sort
排序。根据注释我们知道他不会对small lists
方法进行排序因为他不可变。
第三步:给非small lists
方法列表打标记调用setFixedUp
方法。
我们不妨来打印下,看看排序前和排序后的方法名称和地址。同时为了控制使我们自己的类的方法我们在上面的methodizeClass
方法添加的打印里打上一个断点,只有来到这个断点的时候证明是我们自己的类ZYPerson
的方法。这个时候我们再清空打印,并且在for循环打印的代码后面加上一个断点(此断点随意打在打印后的代码就可)为了避免系统后面的方法也打印出来。
让我们要观察的方法打印出来。如图6:
6.png打印结果:如图7
7.png到这里我们prepareMethodLists
方法的主要流程就走完了。
下面我们回到prepareMethodLists
方法入口methodizeClass
方法里。在下一行代码打上断点查看走向。如图8
我们看到rwe
在这个方法里为NULL
。所以不会走下面的if
方法attachLists
。那么这个rwe
是什么时候才会赋值的呢?我们放到文章后面来探讨。
至此我们可以理一遍流程:
第一步:从_read_images
方法进入 查找到关于类的方法readClass
进行了类的名字和类的地址绑定;
第二步:走到另一部分代码 进行非懒加载类的加载处理 然后进入方法 realizeClassWithoutSwift
进行ro
、rw
处理和父类
、元类
、isa走位
绑定处理。然后进入方法处理方法methodizeClass
;
第三步:在这个方法进行方法
、属性
、分类
的处理,我们只是跟踪到了方法处理,进入到了方法预处理方法prepareMethodLists
第四步:对方法进行遍历然后调用方法修复方法fixupMethodList
第五步:fixupMethodList
方法对方法进行名字
和sel
绑定,并且排序处理。
这就是上面我们探索的内容。
总结:回归到我们之前为我们的类添加load
方法,我们不加load
方法是不会走我们read_images
里的非懒加载类的处理流程。苹果这样做的目的就是为了节约内存
,为了使得在开发过程中的类得到区分,把一些类定义为懒加载类(没有实现load
方法的),在这些懒加载类没有用到之前都不会去加载和处理。这样就会使得内存得到大量的结余。启动时间更短运行更快。
疑问:既然上面我们谈到非懒加载类会走上面的处理流程,那懒加载类呢?懒加载类怎么处理的呢?
懒加载类加载处理流程
我们从上面非懒加载类的处理流程从read_images里的非懒加载类判断入口进入到realizeClassWithoutSwift
方法。之后进行一些列的类处理。那我们猜测是否在其他的地方也有有入口进入realizeClassWithoutSwift
这个方法呢?我们就在这个方法里之前添加的特殊打印的地方加一个断点。并且把ZYPerson
的load
方法屏蔽掉.如果真的来了,那我们就利用bt
命令来查看堆栈信息,追溯流程。
从堆栈流程我们发现是:
lookUpImpOrForward
->realizeAndInitializeIfNeeded_locked
->initializeAndLeaveLocked
->initializeAndMaybeRelock
->realizeClassMaybeSwiftAndUnlock
->realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
从这个流程进入了我们上面分析的类加载处理流程。
那么是什么时机调用的上面的流程呢?
分析:我们从上面可以知道,当进入上面bt
流程的时候ZYPerson
只是进行了alloc
,并没有去调用下面的方法。这时候就已经进入到这里了。
结论:所以我们可以确定懒加载类是在第一次消息发送的时候就会去加载这个相关的类。
总结:
下面我用两张图片来总结归纳懒加载和非懒加载类的加载处理过程:
类加载流程.png分类(categofies
)提前预告:
我们在上面探索类的加载流程的时候在methodizeClass
方法里根据断点跟踪看到预处理方法列表后有判断rwe是否存在,那个时候我们发现是不存在的,那影响这个的因素是什么呢?而且我们在进入methodizeClass
方法前看到他的注释是Attach categories
这不得不让我们引发思考就是这里面的某些东西是否是跟分类有关的呢?下面我们来到main.m
文件在main
函数上方添加一个分类ZYPerson (ZY)
给这个类设置两个属性和三个方法。然后我们利用 clang -rewrite-objc main.m -o main.cpp
命令将main.m
文件转成c++
文件看看其真实存在是什么样的。
main.m
@interface ZYPerson (ZY)
@property (nonatomic, copy) NSString *zy_name;
@property (nonatomic, assign) int zy_age;
- (void)zy_instanceMethod1;
- (void)zy_instanceMethod2;
+ (void)zy_classMethod3;
@end
@implementation ZYPerson (ZY)
- (void)zy_instanceMethod1
{
NSLog(@"%s",__func__);
}
- (void)zy_instanceMethod2
{
NSLog(@"%s",__func__);
}
+ (void)zy_classMethod3
{
NSLog(@"%s",__func__);
}
@end
main.cpp
:
我们直接command+下
来到最后一行代码,然后我们可以看到这样的一行代码:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_ZYPerson_$_ZY,
};
在这里我们看到_CATEGORY_ZYPerson_
拼接一个_ZY
,就是我们自己创建的那个ZYPerson(ZY)
的分类。我们搜索下_category_t
这个结构体
_category_t
:
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结构体的成员变量里看到了一个名字
name
,应该就是存储ZY
这个字符;还有class
,应该就是ZYPerson
类;然后就是实例方法
、类方法
、协议
和属性
。
在这里我们发现类方法居然是这些实例方法放一起的。不过我们想到分类是没有元类这一点也就能够理解了。因为没有元类所以他的类方法只能直接和实例方法放在一起了。
我们继续搜索下_category_t
分类的东西
static struct _category_t _OBJC_$_CATEGORY_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"ZYPerson",
0, // &OBJC_CLASS_$_ZYPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_ZYPerson_$_ZY,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_ZYPerson_$_ZY,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_ZYPerson_$_ZY,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_ZYPerson_$_ZY,
};
在这个我们自己分类ZYPerson(ZY)的结构体中看到有一个名字记录的居然是
ZYPerson
?然后class
记录的居然是个0
?其实这里应该只是做一个占位作用,因为class
不可能为0
,而那个name
也不应该是ZYPerson
而应该是ZY
,唯一的解释就是在编译阶段还不能确定这个名字和类。所以只是占位。然后看到有实例方法
、类方法
、协议
和属性
,和上面的结构体模型结构是一致的。
我们继续查看下关于分类的东西
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_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"zy_instanceMethod1", "v16@0:8", (void *)_I_ZYPerson_ZY_zy_instanceMethod1},
{(struct objc_selector *)"zy_instanceMethod2", "v16@0:8", (void *)_I_ZYPerson_ZY_zy_instanceMethod2}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"zy_classMethod3", "v16@0:8", (void *)_C_ZYPerson_ZY_zy_classMethod3}}
};
从上面的两段代码我们可以发现,虽然分类的实例方法和类方法放在一起,但是他们在实现的时候是分开的。
协议_protocol_t
:
struct _protocol_t _OBJC_PROTOCOL_NSObject __attribute__ ((used)) = {
0,
"NSObject",
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSObject,
0,
(const struct method_list_t *)&_OBJC_PROTOCOL_OPT_INSTANCE_METHODS_NSObject,
0,
(const struct _prop_list_t *)&_OBJC_PROTOCOL_PROPERTIES_NSObject,
sizeof(_protocol_t),
0,
(const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSObject
};
struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSObject = &_OBJC_PROTOCOL_NSObject;
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSObject
};
属性_prop_list_t
:
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_ZYPerson_$_ZY __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
2,
{{"zy_name","T@\"NSString\",C,N"},
{"zy_age","Ti,N"}}
};
在查找属性的时候我们并没有发现他的
setter/getter
方法。这也从侧面反映了我们经常提到的分类不能设置属性的说法,是因为它不会生成setter/getter
方法,所以我们才用关联对象的方法来实现。
我们发现分类元类在编译阶段就已经做了一些处理,尤其在类方法、属性等特殊点上做了特殊的处理。所以我们下一篇文章就一起去探索下分类的一些实现原理。
至此,文章告一段落,原创码字不易,如能给您带来些许启发那也是给作者的极大鼓励。也盼望需要转载的朋友请标注出处,谢谢!
遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。