在上一篇文章(dyld和objc的关联)中,
我们我已经了解到objc
的初始化方法_objc_init
,本文的主题便是由_objc_init
开始,一步步探究类是如何从Mach中加载到内存的。
_objc_init的实现内容
我们贴上_objc_init
的实现代码。(代码来源objc-7.8.1
)
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
// 什么时候调用? images 镜像文件
// map_images()
// load_images()
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
根据注释和代码,这个方法中每一个方法调用实现的内容为:
- environ_init(): 读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助
- tls_init(): 关于线程key的绑定---比如每线程数据的析构函数
-
static_init(): 运行C++静态构造函数。在dyld调用我们的静态构造函数之前,
libc
会调用_objc_init()
,因此我们必须自己做 - lock_init(): 没有重写,采用C++的特性
-
exception_init(): 初始化
libobjc
的异常处理系统 -
runtime_init(): runtime运行时环境初始化,里面是
unattachedCategories
,allocatedClasses
- exception_init(): 缓存条件初始化
-
_imp_implementationWithBlock_init(): 启动回调机制,通过这不会做什么,因为所有的初始化都是惰性的,都是对于某些进程,我们会迫不及待地加载
trampolines dylib
- _dyld_objc_notify_register: 在dyld中注册回调函数
在这里没有看到有关类的初始化的相关代码,那么实现的代码可能就在注册在dyld中的回调函数中,我们在上一篇文章已经知道load_images
中时调用类的load
方法,并没有类的初始化相关代码,那么类的初始化就有可能在map_images
中实现。(也可以顾名思义,我们从Mach-O加载内存实际上是加载到一张map)
map_images的调用时机
map_images
是注册到dyld
的回调方法,那么dyld
是在何时调用的呢,我们通过dyld
代码一步步探究。
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
map_images
赋值给了sNotifyObjCMapped
,全局搜索sNotifyObjCMapped
,在notifyBatchPartial
找到调用
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
...
if ( objcImageCount != 0 ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
uint64_t t0 = mach_absolute_time();
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
uint64_t t1 = mach_absolute_time();
ImageLoader::fgTotalObjCSetupTime += (t1-t0);
}
...
}
全局搜索notifyBatchPartial
,可以看到registerObjCNotifiers
,也就是注册回调函数的时候,便会调用一遍这个方法,所以,当map_images
注册到dyld
时,dyld
便会尝试调用一遍这个方法,而map_images
的执行肯定在load_images
之前。
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
map_images的调用流程
进入map_images
的实现
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
继续map_images_nolock
方法,
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
...
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
...
}
这个方法代码较多,我们可以找到一个关键的方法---_read_images
。这个方法便是我们探究类的加载关键的方法
_read_images
_read_images
的代码较多,下面贴上简略的代码。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
bool launchTime = NO;
TimeLogger ts(PrintImageTimes);
runtimeLock.assertLocked();
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
// 只执行一次
if (!doneOnce) {
...
}
// Fix up @selector references
// 简单的字符串 -- 地址 字符串
static size_t UnfixedSelectors;
{
...
}
ts.log("IMAGE TIMES: fix up selector references");
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
...
}
ts.log("IMAGE TIMES: discover classes");
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
if (!noClassesRemapped()) {
...
}
ts.log("IMAGE TIMES: remap classes");
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
...
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
...
}
ts.log("IMAGE TIMES: discover protocols");
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {
...
}
ts.log("IMAGE TIMES: fix up @protocol references");
// Discover categories. Only do this after the initial category
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/5311914r5
if (didInitialAttachCategories) {
...
}
ts.log("IMAGE TIMES: discover categories");
// 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) {
...
}
ts.log("IMAGE TIMES: realize non-lazy classes");
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
...
}
ts.log("IMAGE TIMES: realize future classes");
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// Print preoptimization statistics
if (PrintPreopt) {
...
}
#undef EACH_HEADER
}
这个方法代码也是不少,总结一下它做了以下事情
- 条件控制进行一次的加载
- 修复预编译阶段的“@selector”混乱问题
- 错误混乱类的处理
- 修复重映射一些没有被镜像文件加载进来的类
- 修复一些消息
- 当我们的类里面有协议的时候:readProtocol
- 修复没有被加载的协议
- 分类处理
- 类的加载处理
- 优化没有被处理的类和被侵犯的类
忽视分类以及协议,我们重点研究我们自定义的类加载的过程。
NXCreateMapTable -- 创建map保存类信息
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
这里创建这里一个大小为总类数量4/3的表,用来保存所有类型的信息
readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName();
if (missingWeakSuperclass(cls)) {
// No superclass (probably weak-linked).
// Disavow any knowledge of this subclass.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->superclass = nil;
return nil;
}
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct.
// Preserve future's rw data block.
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->name);
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(getClassExceptSomeSwift(mangledName));
} else {
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
这个方法看起来像真正进行类的加载的方法,可以看到这里有对类bits的ro和rw进行赋值的过程,然而我们通过断点调试,发现程序并没有进入到ro和rw的赋值部分,实际这个方法中只是简单地存储了类的地址和名字。
这个方法具体执行了那些操作呢,我们通过addNamedClass
和addClassTableEntry
这两个方法来一窥究竟。
- addNamedClass
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}
程序会来到NXMapInsert
,在这个方法里会将类添加到一开始创建的gdb_objc_realized_classes
中
- addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
这个方法里,类会被添加到objc的allocatedClasses
这个集合里,allocatedClasses
便是我们一开始runtime_init
时便已经创建好的集合,另外这个方法里还会通过类的isa找到类的元类,将当前类的元类也添加到这个集合中。
经过readClass
后,类便从Mach-O读取到内存,也就是一张表中,但是目前内存中的类仅有地址和名字这两个数据,而Mach-O中的数据仍未读取。
realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) return cls;
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
// data() -> ro
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);
}
#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.
// cls 信息 -> 父类 -> 元类 : cls LGPerson
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), 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->name, "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->superclass &&
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->superclass = 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;
}
这个方法便是将类的信息完全加载出来的的方法,在_read_images
中有两处对该方法调用,而通过断点调试我们发现调用的是第一个realizeClassWithoutSwift
进行类的加载。
一般来说,后面凡是带“Future”字样的条件执行内容我们都不必进行探究,通过断点调试发现,程序并不会进入到Future的内容。
为了针对性研究自定义类,我们创建一个类并实现他的load
方法(因为没有实现load方法的类为懒加载的类,并不会在mian函数之前初始化), 并且在realizeClassWithoutSwift
开始的地方添加一段代码打上断点,通过断点调试探究。
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;
- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;
+ (void)kc_sayClassMethod;
@end
...
#import "LGPerson.h"
@implementation LGPerson
+ (void)load{
}
- (void)kc_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod3{
NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
NSLog(@"%s",__func__);
}
@end
4A73ED7F-6B54-4437-A685-A4A885A8C0D2.png
运行程序,往下执行断点来到
299E324D-0D52-4C87-857A-4AD234EAFBFF.png这个过程中,Mach-O中类的data
被直接赋值给了ro
,ro
在赋值给了rw,之后在将rw
赋值给cls
的bits
,而rw
中除了ro
,还有一个rwe
,如果rwe
有数据,那么ro
会复制一份存放在rwe
中。
我们可以rw
中ro
的get方法得出以上结论
// 外界读取ro的方法
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
// 如果rwe中有数据,返回rwe中的ro
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
// rwe没有数据,返回rw中的ro
return v.get<const class_ro_t *>();
}
ro表示readonly,指类的clean Menory,这块内存初始化完成后便不会被修改,rwe一块dirty Menory,这块区域中的数据会被修改,在苹果WWDC-2020的内存优化说明中,对这一块有详细的介绍。
处理好ro和rw后,方法来到了递归调用,这里会通过cls
的superclass
和isa
找到父类和元类对象,通过递归调用realizeClassWithoutSwift
初始化,从而确定类和元类的继承链。
确定好继承链后便是一系列isa处理后,方法来到了methodizeClass
,这个方法做了什么呢?我们进入探究。
methodizeClass
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();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name));
}
ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name);
}
#endif
}
同样为了针对性研究,我们同样在methodizeClass
方法中添加代码研究。
首先来到是方法的处理,这里拿到ro
的baseMethod
,如果有值,则执行prepareMethodLists
处理方法列表,如果rwe
有值,则通过调用methods
的attachLists
将处理后的方法列表添加到rwe
。中,
7599D2B2-2F11-4689-86C3-3CCD40C077DA.png因为本文探讨的类没有添加分类,所以
rwe
为空,有关rwe
的内容下一篇文章会做探讨,本文不做探究
打印list
的内容,可以看到里面存放是类的所有实例方法信息
(lldb) p *list
(method_list_t) $0 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 8
first = {
name = "kc_instanceMethod2"
types = 0x0000000100000eb8 "v16@0:8"
imp = 0x0000000100000c50 (KCObjc`-[LGPerson kc_instanceMethod2])
}
}
}
(lldb) p $0.get(0)
(method_t) $2 = {
name = "kc_instanceMethod2"
types = 0x0000000100000eb8 "v16@0:8"
imp = 0x0000000100000c50 (KCObjc`-[LGPerson kc_instanceMethod2])
}
(lldb) p $0.get(1)
(method_t) $3 = {
name = "kc_instanceMethod1"
types = 0x0000000100000eb8 "v16@0:8"
imp = 0x0000000100000c80 (KCObjc`-[LGPerson kc_instanceMethod1])
}
(lldb) p $0.get(2)
(method_t) $4 = {
name = "kc_instanceMethod3"
types = 0x0000000100000eb8 "v16@0:8"
imp = 0x0000000100000cb0 (KCObjc`-[LGPerson kc_instanceMethod3])
}
(lldb) p $0.get(3)
(method_t) $5 = {
name = "kc_name"
types = 0x0000000100000ece "@16@0:8"
imp = 0x0000000100000ce0 (KCObjc`-[LGPerson kc_name])
}
(lldb) p $0.get(4)
(method_t) $6 = {
name = "setKc_name:"
types = 0x0000000100000ed6 "v24@0:8@16"
imp = 0x0000000100000d10 (KCObjc`-[LGPerson setKc_name:])
}
(lldb) p $0.get(5)
(method_t) $7 = {
name = "kc_age"
types = 0x0000000100000ee1 "i16@0:8"
imp = 0x0000000100000d40 (KCObjc`-[LGPerson kc_age])
}
(lldb) p $0.get(6)
(method_t) $8 = {
name = "setKc_age:"
types = 0x0000000100000ee9 "v20@0:8i16"
imp = 0x0000000100000d60 (KCObjc`-[LGPerson setKc_age:])
}
(lldb) p $0.get(7)
(method_t) $9 = {
name = ".cxx_destruct"
types = 0x0000000100000eb8 "v16@0:8"
imp = 0x0000000100000d80 (KCObjc`-[LGPerson .cxx_destruct])
}
prepareMethodLists
中做了哪些事情呢?我们进入方法中查看
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
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());
}
// 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);
}
}
除去一些断言和赋值操作,我们看一个关键的方法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.name = sel_registerNameNoLock(name, bundleCopy);
}
}
// Sort by selector address.
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
// Mark method list as uniqued and sorted
mlist->setFixedUp();
}
可以看到方法的排序是根据SEL的地址从低到高排序的。
方法处理完成后,类后面还会进行协议和属性的处理,至此,类的加载已基本完成。但我们目前探究的是非懒加载的类,那么懒加载的类又是何时加载的呢?
懒加载类的加载
大多数情况下,我们所创建的类都是懒加载的类(即类没有实现load方法),如果我们没有给这个类发送任何消息,那么这个类永远不会被加载。为了探究它是何时被加载进内存的,我们可以在已经确定的类的加载关键方法realizeClassWithoutSwift
里打上断点,并给当前类发送消息,看看他是何时加载进内存的。
LGPerson *person = [LGPerson alloc];
[person kc_instanceMethod1];
进入断点后,用lldb打印调用堆栈
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
frame #0: 0x00000001002de6c6 libobjc.A.dylib`realizeClassWithoutSwift(cls=LGPerson, previously=0x0000000000000000) at objc-runtime-new.mm:2553:66
frame #1: 0x00000001002fb33a libobjc.A.dylib`realizeClassMaybeSwiftMaybeRelock(cls=LGPerson, lock=0x0000000100337000, leaveLocked=false) at objc-runtime-new.mm:2833:9
frame #2: 0x00000001002e98cf libobjc.A.dylib`realizeClassMaybeSwiftAndUnlock(cls=LGPerson, lock=0x0000000100337000) at objc-runtime-new.mm:2850:12
frame #3: 0x00000001002ddff0 libobjc.A.dylib`initializeAndMaybeRelock(cls=0x0000000100002240, inst=0x0000000100002268, lock=0x0000000100337000, leaveLocked=true) at objc-runtime-new.mm:2162:19
frame #4: 0x00000001002e695a libobjc.A.dylib`initializeAndLeaveLocked(cls=0x0000000100002240, obj=0x0000000100002268, lock=0x0000000100337000) at objc-runtime-new.mm:2187:12
* frame #5: 0x00000001002e66a1 libobjc.A.dylib`lookUpImpOrForward(inst=0x0000000100002268, sel="alloc", cls=0x0000000100002240, behavior=3) at objc-runtime-new.mm:6257:15
frame #6: 0x00000001002c1bd9 libobjc.A.dylib`_objc_msgSend_uncached at objc-msg-x86_64.s:1101
frame #7: 0x000000010031d0c5 libobjc.A.dylib`objc_alloc [inlined] callAlloc(cls=LGPerson, checkNil=true, allocWithZone=false) at NSObject.mm:1714:12
frame #8: 0x000000010031d01e libobjc.A.dylib`objc_alloc(cls=LGPerson) at NSObject.mm:1730
frame #9: 0x0000000100000bdb KCObjc`main(argc=1, argv=0x00007ffeefbff4b0) at main.m:56:28 [opt]
frame #10: 0x00007fff717dacc9 libdyld.dylib`start + 1
可以看到,懒加载的类,直到我们调用第一次调用方法时,经过慢速查找lookUpImpOrForward
查找方法时,方向当前类并没有初始化,便会通过realizeClassMaybeSwiftAndUnlock
->realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
,从而将类加载到内存中。
总结
综上,类的加载流程可以由下图表示
类的加载.png
网友评论