iOS 应用的加载objc
篇
前言
在上一篇文章应用加载分析中我们经过对dlyd
的探索最后引入到了objc
中的_objc_init
函数,那么_objc_init
到底做了什么呢?下面我们将对_objc_init
进行进一步探索,并通过探索来了解一下应用加载在obcj
中都做了什么。
注: 本文使用的是objc4-779.1
1. _objc_init探索
_objc_init源码:
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
- 首先判断是否已经初始化,如果已经初始化了则直接返回。
2.1 environ_init
environ_init 源码:
/***********************************************************************
* environ_init
* Read environment variables that affect the runtime.
* Also print environment variable help, if requested.
**********************************************************************/
void environ_init(void)
{
if (issetugid()) {
// All environment variables are silently ignored when setuid or setgid
// This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
return;
}
bool PrintHelp = false;
bool PrintOptions = false;
bool maybeMallocDebugging = false;
// Scan environ[] directly instead of calling getenv() a lot.
// This optimizes the case where none are set.
for (char **p = *_NSGetEnviron(); *p != nil; p++) {
if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) ||
0 == strncmp(*p, "NSZombiesEnabled", 16))
{
maybeMallocDebugging = true;
}
if (0 != strncmp(*p, "OBJC_", 5)) continue;
if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
PrintHelp = true;
continue;
}
if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
PrintOptions = true;
continue;
}
const char *value = strchr(*p, '=');
if (!*value) continue;
value++;
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if ((size_t)(value - *p) == 1+opt->envlen &&
0 == strncmp(*p, opt->env, opt->envlen))
{
*opt->var = (0 == strcmp(value, "YES"));
break;
}
}
}
// Special case: enable some autorelease pool debugging
// when some malloc debugging is enabled
// and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
if (maybeMallocDebugging) {
const char *insert = getenv("DYLD_INSERT_LIBRARIES");
const char *zombie = getenv("NSZombiesEnabled");
const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
if ((getenv("MallocStackLogging")
|| getenv("MallocStackLoggingNoCompact")
|| (zombie && (*zombie == 'Y' || *zombie == 'y'))
|| (insert && strstr(insert, "libgmalloc")))
&&
(!pooldebug || 0 == strcmp(pooldebug, "YES")))
{
DebugPoolAllocation = true;
}
}
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if (PrintHelp || PrintOptions) {
if (PrintHelp) {
_objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
_objc_inform("OBJC_HELP: describe available environment variables");
if (PrintOptions) {
_objc_inform("OBJC_HELP is set");
}
_objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
}
if (PrintOptions) {
_objc_inform("OBJC_PRINT_OPTIONS is set");
}
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
}
我们可以看到environ_init
主要是读取一些环境变量以及打印环境变量的一些帮助信息。
2.2 tls_init
tls_init源码:
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
此处做的是一些关于线程key
的绑定,比如每个线程数据的析构函数。
2.3 static_init
static_init 源码:
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
这里会运行c++静态构造函数。在dyld
调用我们的静态构造函数之前,libc会调用_objc_init(),所以我们必须自己做。并且这里只会初始化系统内置的C++
静态构造函数,我么自己代码里面写的并不会在这里初始化。
2.4 runtime_init
runtime_init源码:
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
运行时的初始化。分类哈希表和allocated
的类的哈希表初始化。
2.5 exception_init
exception_init源码:
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
这里做的是初始化一个异常处理系统,我们诚信触发的异常都会来到这里:
old_terminate源码实现:
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
old_terminate
是处理异常的回调其流程如下:
- 判断是否是一个活跃的异常
- 如果是活跃的,检查是否是
objc
抛出的异常 - 如果是
objc
抛出的异常,调用uncaught_handler
回调函数指针 - 如果不是
objc
抛出的异常,就继续C++
的异常终止操作
2.6 cache_init
cache_init 源码:
void cache_init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
mach_msg_type_number_t count = 0;
kern_return_t kr;
while (objc_restartableRanges[count].location) {
count++;
}
kr = task_restartable_ranges_register(mach_task_self(),
objc_restartableRanges, count);
if (kr == KERN_SUCCESS) return;
_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
这里做的主要是缓存的一些初始化。
2.7 _imp_implementationWithBlock_init
_imp_implementationWithBlock_init 源码:
/// Initialize the trampoline machinery. Normally this does nothing, as
/// everything is initialized lazily, but for certain processes we eagerly load
/// the trampolines dylib.
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
// Eagerly load libobjc-trampolines.dylib in certain processes. Some
// programs (most notably QtWebEngineProcess used by older versions of
// embedded Chromium) enable a highly restrictive sandbox profile which
// blocks access to that dylib. If anything calls
// imp_implementationWithBlock (as AppKit has started doing) then we'll
// crash trying to load it. Loading it here sets it up before the sandbox
// profile is enabled and blocks it.
//
// This fixes EA Origin (rdar://problem/50813789)
// and Steam (rdar://problem/55286131)
if (__progname &&
(strcmp(__progname, "QtWebEngineProcess") == 0 ||
strcmp(__progname, "Steam Helper") == 0)) {
Trampolines.Initialize();
}
#endif
}
这里是懒加载的方式初始化trampoline machinery
2.8 _dyld_objc_notify_register
这里就是我们的重点了,它的定义如下:
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
译:
- 注意:仅供
objc
运行时使用。- 当
objc
镜像被映射(mapped)、取消映射(unmapped)和初始化(initialized)注册的回调函数就会被调用。dyld
将为镜像中objc-image-info
节中的数组回调映射(mapped)函数。- 这些些
dylibs
的镜像会自动增加引用计数,所以objc
不再需要处理。- 在调用
_dyld_objc_notify_register
函数的过程中,会对它们调用dlopen
函数,以防止它们被卸载。- 在以后任何调用
dlopen
的期间,dyld
将优先使用已加载的objc
镜像调用映射(mapped)函数dyld
也会调用映射(mapped)函数,当调用dyld
时,dyld
将调用init
函数。- 当
objc
调用任何+load
方法时,该镜像中的初始值将被设定
_dyld_objc_notify_register
函数的三个参数mapped
、init
、unmapped
其实都是函数指针,我们点击参数类型一看便知。其实就在函数上面定义的。
这三个函数指针都是在dyld
中回调的,我们点击_dyld_objc_notify_register
跳转到它的实现源码一看便知,这里调用了registerObjCNotifiers
函数。
接着来到dyld
的registerObjCNotifiers
函数中进行查看
在registerObjCNotifiers
函数中首先就是赋值这三个函数的指针。定义如下,跟我们在objc
中看到的一模一样。
通过这几张截图内容可以说明在registerObjCNotifiers
的内部,libObjc
传过来的三个函数指针被dyld
保存在了本地静态变量中,最终函数是否被调用,取决于这三个静态变量,在registerObjCNotifiers
中的try
中我们通过如下注释:
// call 'mapped' function with all images mapped so far
再此调用
mapped
函数来映射所有镜像
那么也就是说notifyBatchPartial
里面会进行真正的函数指针调用,下面我们来看看,由于又是个大的方法代码众多我们只看重点:
在这里我们可以看到tell objc about new images
告诉objc
镜像已经映射完成了。在箭头的地方就是真正调用指针的地方。那么这三个函数的的调用关系就基本理清楚了,但是这三个函数具体做了什么呢?下面我们一一探索。
3. 探索 map_images
首先我们查看一下map_images
的源码实现:
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
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);
}
根据注释我们可以知道,这个函数是处理dyld
映射给定的镜像,跟我们上面分析的一致,然后它还可以在获取到特定ABI
的锁后调用与ABI
无关的代码。
由于代码很简单,肯定不是核心的实现,我们继续向下探索来到map_images_nolock
函数:
乍一看150多行代码,也不少了,但是仔细一看,大部分代码都是进行镜像文件信息的提取,以及各种环境的判断,只有_read_images
才是该函数最核心的。在这里hCount
就是header count
的意思,表示Mach-O
中header
的数量。
3.1 _read_images
那么我们看看_read_images
的定义吧:
这里通过_read_images
函数对连接的header
进行初始化,从头开始进一步处理。这里我们发现_read_images
有400行左右的代码,我们不妨折叠一下代码,看看有多少个分支,先总体预览一下:
通过折叠代码,我们可以大致将_read_images
分为如下的几个流程:
3.2 doneOnce
顾名思义,这里只会执行一次,那么这一次都做了些什么事情呢?
- 首先将
doneOnce
和launchTime
置为YES
- 然后通过
SUPPORT_NONPOINTER_ISA
这个宏判断当前是否支持开启内存优化的isa
,如果支持则在某些条件下需要禁用这个优化 - 然后通过
SUPPORT_INDEXED_ISA
判断当前是否将类存储在isa
作为类表的索引,如果是的话,在遍历所有的Mach-O
的头部,对Swift3.0
之前的代码禁用isa
的内存优化
- 通过宏
TARGET_OS_OSX
判断是否是macOS
执行环境,不是则不处理 - 是
macOS
执行环境,如果版本小于10.11则需要禁用掉nonPointerIsa
,原因是APP过于老旧 - 然后在遍历
Mach-O
的头部,判断如果有__DATA,__objc_rawisa
段的存在,则禁用nonPointerIsa
,原因是新APP加载老的扩展的时候会需要这样的判断操作
- 如果关闭了
Tagged Pointers
则进行一些处理 - 用随机性的方式初始化
objc_debug_taggedpointer_obfuscator
- 计算一个合适大小的
size
作为表的容量,这里使用3/4这个装载因子来作为扩容的临界值 - 初始化
gdb_objc_realized_classes
哈希表,用来加载所有的类
表的定义如下:
// This is a misnomer: gdb_objc_realized_classes is actually a list of
// named classes not in the dyld shared cache, whether realized or not.
NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
译:这是一个误称,
gdb_objc_realized_classes
实际上存的是不在dyld
共享缓存里面的类,无论这些类是否实现。
3.3 Fix up @selector references
这里还会修复
SEL
引用,主要就是注册SEL
。通过调用sel_registerNameNoLock
函数最终调用__sel_registerName
函数进行真正的注册。
- 先加锁,然后遍历
EACH_HEADER
- 如果开启了预优化,就跳转到下一个
- 如果没有则获取当前镜像是否是
Bundle
类型 - 然后通过
_getObjc2SelectorRefs
函数拿到所有SEL
的引用 - 然后对所有的
SEL
引用调用sel_registerNameNoLock
函数进行注册
__sel_registerName 源码:
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
SEL result = 0;
if (shouldLock) selLock.assertUnlocked();
else selLock.assertLocked();
if (!name) return (SEL)0;
result = search_builtins(name);
if (result) return result;
conditional_mutex_locker_t lock(selLock, shouldLock);
auto it = namedSelectors.get().insert(name);
if (it.second) {
// No match. Insert.
*it.first = (const char *)sel_alloc(name, copy);
}
return (SEL)*it.first;
}
__sel_registerName
流程:
- 判断是否要加锁,如果需要就加锁
- 如果传入的
name
为空就返回SEL0
,也就是空SEL
,因为在底层SEL
就是一个数值类型 - 通过
search_builtins
函数获取一个result
,如果已经注册就直接返回 - 加锁,然后获取
namedSelectors
哈希表,获取的过程中没有就会创建 - 获取到后则插入,判断
second
的值,这里应该是second
为true
则表示没有匹配上,没匹配上就通过sel_alloc
来创建一个SEL
赋值到哈希表的缓存中(PS:大概意思明白,但是it
的数据结构没太弄明白,为什么这样用?) - 最后返回刚刚
alloc
的SEL
3.4 Discover classes. Fix up unresolved future classes. Mark bundle classes.
这里是发现那些未解析的
future
类,标记bundle
类,如果没有则通过调用readClass
函数将类
- 首先获取一个
bool
值hasDyldRoots
用来在进入到循环后通过mustReadClasses
函数判断那些Image
被充分优化,如果被优化就可以跳过这个步骤,一般不会来到这里 - 然通过
_getObjc2ClassList
函数来获取到Mach-O
中类的列表 - 读取
header
是否是**Bundle**
,读取header
是否开启了优化 - 然后遍历获取到的类,通过
readClass
处理是否是future
类的修复,然后返回一个新类,判断返回的新类和列表类是否一致,如果不一致并且新类不为空,则说明被修复过了,则重新为类开辟内存,添加到数组中(初始化所有懒加载的类需要的内存空间 - 现在数据没有加载到的 - 连类都没有初始化的)
readClass 源码:
/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be:
* - cls
* - nil (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
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->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;
}
readClass 分析:
- 首先获取
mangled name
- 然后判断是否是弱引用的父类,如果是则调用
addRemappedClass
函数将其移除,并将其父类置空,返回一个nil
- 对于
Swift
向后兼容的修复 - 如果是
future
类就会进行rw、ro
的处理,基本不会来到这里,我们并不会有太多的future
类,所以cls
也基本不会被赋值为newCls
- 接下来进行断言处理,也基本不会来,都是出错了才会到这里,正常流程是不会走的
- 下面就是将类信息通过调用
addNamedClass
和addClassTableEntry
分别放入到我们doneOnce
和runtime_init
初始化的初始化的哈希表中 - 最后做一个共享缓存从不包含
MH_BUNDLEs
的处理
小结:
所以readClass
主要就是处理future
类,设置future
类的ro、rw
,然后将类插入到gdb_objc_realized_classes
类总表和allocatedClasses
已开辟内存的类的表。但是我们用到future
类的时候很少,所以这里面主要代码使用也就很少。这些都可以通过lldb
断点调试进行验证,这里就不上验证的截图了,只说一下原理。
3.5 Fix up remapped classes
这里是修复重新映射的类,类表和非懒加载的类表没有被重映射的(也就是
_objc_classlist
) 由于消息转发,类引用和父类引用会被重映射 (也就是_objc_classrefs
)(注:一般不会来到这里)
- 通过
noClassesRemapped
判断是否有类引用(_objc_classrefs
)需要重映射,如果需要,则遍历EACH_HEADER
- 通过
_getObjc2ClassRefs
和_getObjc2SuperRefs
分别取出当前镜像的类引用和父类引用,然后调用remapClassRef
函数进行重新映射。
3.6 Fix up old objc_msgSend_fixup call sites
这里是修复旧的
objc_msgSend_fixup
调用
- 首先还是通过一个宏
SUPPORT_FIXUP
来判断是否需要修复 - 然后循环遍历
EACH_HEADER
,通过_getObjc2MessageRefs
函数获取当前镜像中的消息引用 - 然后通过遍历这些消息调用
fixupMessageRef
函数进行修复
fixupMessageRef 源码如下
/***********************************************************************
* fixupMessageRef
* Repairs an old vtable dispatch call site.
* vtable dispatch itself is not supported.
**********************************************************************/
static void
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == @selector(retain)) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == @selector(release)) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == @selector(autorelease)) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
在fixupMessageRef
函数中有一个重要的地方就是把alloc
指向了objc_alloc
,这个在我们探索alloc
的时候有关系到。
iOS Objective-C alloc 调用前会调用id objc_alloc(Class cls)的原因
3.7 Discover protocols. Fix up protocol refs.
这里是遍历所有协议,将协议列表加载到
protocol
的哈希表中
- 首先还是初始化一些类信息,断言判断
- 通过
protocols
函数初始化protocol_map
哈希表 - 判断是否开启了预优化,或者是共享缓存中中的协议,这里不会处理
- 获取是否是
Bundle
- 通过
_getObjc2ProtocolList
函数获取到当前镜像文件的所有协议 - 循环遍历这些协议,通过
readProtocol
函数进一步处理
3.7.1 readProtocol
那么readProtocol
函数到底做了些什么处理呢?我们来到该函数一探究竟。
根据注释我们可以知道,该函数的作用是读取由编译器编写的协议。
- 首先根据镜像文件的类型选择
NXMapKeyCopyingInsert
还是NXMapInsert
- 根据
mangledName
取出是否存在oldProtocol
- 如果存在,判断新旧是否相等,相等就不处理了,说明处理过,不相等则则说明我们选择了另一个协议,这里应该是对共享缓存的一些处理,如果是共享缓存里面的需要将新协议清除,这样才不会被别人使用,共享缓存中的不能修改,另外也基本不会来到这个分支进行处理,因为
headerIsPreoptimized
为true
的时候在_read_image
函数中已经continue
了。
这个分支也不会到来,还是上面一样的原因headerIsPreoptimized
为true
的时候在_read_image
函数中已经continue
了,通过全局搜索也没有其他调用的地方。
根据注释我们可以知道共享缓存初始化了协议对象本身,但是为了允许缓存外替换,我们现在需要将它添加到协议表中。
所以这个分支的主要内容就是对协议的一个替换,但是也不会被执行,应该是苹果对未来的处理,现在还没用到。
从未预先优化的镜像中读取出新的协议像分配了足够的存储空间,我们将它修复好(修复isa
)然后存储到Protocol
哈希表中。
从未预先优化的镜像中读取出新的协议,但是没有分配足够的存储空间。我们就重新分配好新协议的存储空间,然后修复好(修复isa
)后将其存储到Protocol
哈希表中。
3.8 Fix up @protocol references
这里是修复@protocol的列表引用,对所有的协议进行重映射,优化后的镜像可能是正确的,但也不确定,所以修复一下。
- 首先还是做个判断,共享缓存且启动时且被预先优化的,可以跳过
- 通过
_getObjc2ProtocolRefs
函数取出协议列表 - 循环遍历通过
remapProtocolRef
进行重映射
remapProtocolRef 源码
/***********************************************************************
* remapProtocolRef
* Fix up a protocol ref, in case the protocol referenced has been reallocated.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref)
{
runtimeLock.assertLocked();
protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
if (*protoref != newproto) {
*protoref = newproto;
UnfixedProtocolReferences++;
}
}
remapProtocol 源码:
/***********************************************************************
* remapProtocol
* Returns the live protocol pointer for proto, which may be pointing to
* a protocol struct that has been reallocated.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static ALWAYS_INLINE protocol_t *remapProtocol(protocol_ref_t proto)
{
runtimeLock.assertLocked();
// Protocols in shared cache images have a canonical bit to mark that they
// are the definition we should use
if (((protocol_t *)proto)->isCanonical())
return (protocol_t *)proto;
protocol_t *newproto = (protocol_t *)
getProtocol(((protocol_t *)proto)->mangledName);
return newproto ? newproto : (protocol_t *)proto;
}
通过remapProtocolRef
和remapProtocol
两个函数实现协议的重映射,以防被引用的协议被重新分配。
3.9 Discover categories.
这里是处理所有的分类
- 首先获取一个是否包含类属性的
bool
值hasClassProperties
- 用了一种我看不懂的C++语法从镜像文件获取分类列表
catlist
- 然后遍历这个列表,首先拿到
分类
和类
还有一个locstamped_category_t
类型的结构体存储分类信息和header info
- 判断
cls
是否存为空,为空则continue
,很好理解,类都不存在就不可能存在分类 - 对于存根类(Stub classes)的处理,存根类在初始化之前并不知道它们的元类,因此我们必须向存根本身添加带有类方法或属性的类别。在调用
methodizeClass
函数的时候将找到它们,并将它们适当地添加到元类中 - 接下来就是正常分类的存储了,首先判断是否有对象方法、协议、实例属性,然后判断类是否实现了(isRealized),如果实现了将这些附加到类中,否则将调用
addForClass
进行处理。 - 然后判断是否有类方法、协议、类属性&&hasClassProperties,将这些附加到元类中,然后判断元类是否实现了(isRealized),如果实现了将这些附加到类中,否则将调用
addForClass
进行处理。
我的另一篇文章iOS 分类的加载会详细的介绍分类的加载过程,以及上述方法的调用时机。
3.10 Realize non-lazy classes
这里是实现非懒加载类的加载,一般实现了
+load
方法和一些静态实例会在此处处理
- 首先还是循环遍历
EACH_HEADER
,并对每一个镜像文件通过_getObjc2NonlazyClassList
函数获取类列表。 - 遍历这个类列表
- 调用
remapClass
获取到类信息 - 非空判断
- 调用
addClassTableEntry
函数将类添加到allocatedClasses
表中 - 如果是
Swift
类并且是Swift metadata initializer
(元数据的初始化),则打印报错信息,因为这样的类不允许是非懒加载的 - 最后调用
realizeClassWithoutSwift
函数
- 调用
3.10.1 realizeClassWithoutSwift
那么realizeClassWithoutSwift
函数具体做了什么呢?点进去一探究竟。又是将近130多行的代码,我们一步一步的分析:
根据注释的意思,我们可以知道这里是对类的首次初始化操作,包括分配ro、rw
,不包含任何的Swift
类的初始化,最后返回类的真实类结构。
这里就很简单了,加锁、初始化一些变量,对类进行非空和已经初始化的判断。
- 这里是通过判断是否是
future
的类来进行处理,一般都会进入到else
进行处理。 - 如果是
future
类则直接给rw
赋值,给ro
赋值,调用changeInfo
函数,修改类信息(一般不会进入该分支) - 如果不是则给
rw
初始化存储空间,给rw
的ro
赋值,设置rw
的flag
,调用setData
函数初始化一些类信息。
- 这里就是处理了一些是元类时的信息
- 选择一个类索引,如果没有就调用
chooseClassArrayIndex
函数进行设置 - 打印一些信息
拿到supercls
(父类)和metacls
(元类)
这里就是对nonPointerIsa
的一些处理
- 首先判断是否是元类,如果是则调用
setInstancesRequireRawIsa
处理 - 如果不是元类则进一步处理
- 设置该类的父类和元类,元类其实就是类的
isa
指向的地方 - 如果是父类,并且不是元类则调用
reconcileInstanceVariables
函数对ro
进行更新 - 如果还没有设置
fastInstanceSize
则设置它 - 拷贝一些
flags
从ro
到rw
- 对一些
flags
进行传递,从ro
或者superclass
(父类) - 给父类添加子类列表,如果没有父类则标记该类为
root
- 最后调用
methodizeClass
,这里面注释是Attach categories
,那么是不是附加分类的东西呢?我们需要进一步探索。
小结:
realizeClassWithoutSwift
就是初始化Objective-C
的类信息,
- 初始化
ro
,以及rw
的部分信息, - 赋值类的父类和元类
- 赋值类的子类
- 调用
methodizeClass
函数
3.10.2 methodizeClass
methodizeClass
函数大约 有70多行代码,我们同样进行一步一步的分析:
根据注释我们可以知道methodizeClass
函数的功能是完善cls
的方法列表,协议列表和属性列表以及对分类的附加。
首先还是加锁,初始化isMetal
、rw
、ro
以及打印一些信息。
把ro
中的method
、property
、protocol
取出来,然后attachLists
到rw
中的methods
、properties
、protocols
中。
- 将分类附加到类上,对于元类和类需要传入不同的附加参数分别是
ATTACH_METACLASS
和ATTACH_CLASS_AND_METACLASS
,如果是元类只附加类方法,如果是类则需要将对象方法附加到类上,将对象方法附加的类的isa即元类上。 - 从类中取出未附加的类进行附加
- debug环境打印信息
PS: 其实按照这个步骤不会走到分类附加这个if
分支,只会调用分支下面那行代码,因为在realizeClassWithoutSwift(cls, nil);
的时候previously
就已经是nil
了,所以该流程不会进行。那么为什么还要写这些代码呢?肯定还有其他调用会进行处理。我们通过全局搜索methodizeClass(
发现该函数的调用处只有realizeClassWithoutSwift
函数中,我们再次全局搜索realizeClassWithoutSwift(
发现使previously
不为空的地方只有_objc_realizeClassFromSwift
函数,如下图:
看来这个函数是处理Swift
相关的,通过注释我们可以知道当该类是Swift
类但是需要执行Objc
的初始化。一个有四种情况:
- cls != nil; previously == cls,cls已经被实现
- cls != nil; previously == nil,cls是运行时创建的
- cls != nil; previously != cls,cls被重新alloc
- cls == nil, previously != nil,cls被取消
但是目前只支持前两种情况
我们尝试搜索_objc_realizeClassFromSwift
的调用,发现并不在objc
中,但是发现如下内容:
/**
* Perform Objective-C initialization of a Swift class.
* Do not call this function. It is provided for the Swift runtime's use only
* and will change without notice or mercy.
*/
#if !(TARGET_OS_OSX && __i386__)
#define OBJC_REALIZECLASSFROMSWIFT_DEFINED 1
OBJC_EXPORT Class _Nullable
_objc_realizeClassFromSwift(Class _Nullable cls, void * _Nullable previously)
OBJC_AVAILABLE(10.14.4, 12.2, 12.2, 5.2, 3.2);
#endif
译: 在
Objc
中不要调用此函数,它仅供Swift
运行时调用,并将在不通知或允许的情况下进行更改。说明这是Swift
运行时和OC
交互的一个函数,在这里我们就不进一步探索了
既然分支调用不到,那么我们看看其下面那行代码调用的函数究竟实现了什么。
attachToClass 源码分析
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
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);
}
}
attachToClass
函数主要是将分类列表通过调用attachCategories
附加到类上,attachCategories
的具体分析将会放到我的另一篇文章iOS Objective-C 分类的加载
小结:
methodizeClass
的主要功能就是完善类信息:
- 将
ro
中的method
attach到rw
的methods
- 将
ro
中的property
attach到rw
的properties
- 将
ro
中的protocol
attach到rw
的protocols
- 对于分类相关的信息通过
attachToClass
进行进一步处理
attachLists 初步探索:
我们看到在上面的函数中最后都是调用attachLists
将ro
中的方法、属性以及协议赋值到rw
中,那么为什么都会用到这个方法呢?
我们点击查看method_list_t
、property_list_t
、protocol_list_t
结果如下:
我们看到method_list_t
、ivar_list_t
、property_list_t
都是继承自entsize_list_tt
,protocol_list_t
虽然没有继承但也一样是个二维数组的结构,我们来到attachLists
函数看看。
attachLists源码:
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
判断要添加的数量是否为 0,如果为 0,直接返回
-
判断当前调用 attachLists 的 list_array_tt 二维数组有多个一维数组
- 如果是,说明是多对多的关系
- 这里会通过 realloc 对容器进行重新分配,大小为原来的大小加上新增的大小
- 然后通过 memmove 把原来的数据移动到容器的末尾
- 最后把新的数据拷贝到容器的起始位置
如果调用 attachLists 的 list_array_tt 二维数组为空且新增大小数目为 1,则直接取 addedList 的第一个 list 返回
-
如果当前调用 attachLists 的 list_array_tt 二维数组只有一个一维数组
- 如果是,说明是一对多的关系
- 这里会通过 realloc 对容器进行重新分配,大小为原来的大小加上新增的大小
- 因为原来只有一个一维数组,所以直接赋值到新 Array 的最后一个位置
- 然后把新数据拷贝到容器的起始位置
3.11 Realize newly-resolved future classes
这里是初始化一些新解析出来的
future
类,应该就是我们在运行时添加的。这也是上面3.4 Discover classes我们处理future
时,如果没有这样的类,这里也是不会来到的。如果有的话就会调用realizeClassWithoutSwift
函数做进一步的处理,然后还会调用setInstancesRequireRawIsaRecursively
函数,将这个类及其所有子类标记为需要原始isa指针。
3.12 realizeAllClasses
这里是实现所有类,但是有个判断,当调试
Non Fragile
的时候才会这样做。
3.13 打印日志
最后就是打印一些日志了,根据该函数加载完成的一些信息进行打印。
3.14 小结
至此_read_images
的流程我们就大概缕清了,下面我们来总结一些核心流程。
- 首先
doneOnce
第一次进来的时候,处理了isa
是否开启内存优化,初始化了gdb_objc_realized_classes
存储所有类的哈希表。 - 方法编号
SEL
的处理,都注册到nameSelectors
表中 - 类的处理,加载所有类到
gdb_objc_realized_classes
表中 - 对类进行重映射
- 修复旧的
objc_msgSend_fixup
调用 - 将所有的协议都添加到
protocol_map
表中 - 重映射所有协议
- 分类处理包括类和元类
- 非懒加载类的初始化,进行
rw、ro
等操作 -
future
类处理 - 当调试
Non Fragile
的时候,实现所有的类 - 打印日志
4. 探索 load_image
接下来我们探索_dyld_objc_notify_register
的第二个参数load_images
,我们在dyld
源码中搜索load_images
对应的sNotifyObjCInit
我们可以看到在dyld
中notifySingle
方法内部sNotifyObjCInit
函数指针被调用了。
下面我们来到objc
源码中进一步探索load_images
load_images 源码:
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
这里是由dyld
映射给定镜像中的+load
方法。
- 首先判断是否有
+load
方法,没有就直接返回,有的话就加锁进行处理 - 调用
prepare_load_methods
函数加载包含+load
方法的类和分类 - 调用
call_load_methods
函数去实现+load
方法的调用
4.1 prepare_load_methods 分析
prepare_load_methods 、schedule_class_load、add_class_to_loadable_list、add_category_to_loadable_list 源码:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes = nil;
static int loadable_classes_used = 0;
static int loadable_classes_allocated = 0;
// List of categories that need +load called (pending parent class +load)
static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;
/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
/***********************************************************************
* add_category_to_loadable_list
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
根据函数名字我们可以大概知道这里是关于实现+load
方法类的预处理,其实就是对于懒加载类和懒加载分类的实现。
- 首先初始化一些变量并加锁
- 通过
_getObjc2NonlazyClassList
函数取出实现+load
方法的类(非懒加载类) - 遍历这些类进行,通过
schedule_class_load
函数递归类的父类,保证父类的+load
方法排在子类签名,在schedule_class_load
函数中,通过调用add_class_to_loadable_list
函数将类的+load
方法存入loadable_classes
中 - 接下来通过
_getObjc2NonlazyCategoryList
函数读取出实现了+load
方法的分类(非懒加载分类) - 遍历这些分类,通过
realizeClassWithoutSwift
函数实现类,因为分类依托于类,类不存在分类也就不存在 - 然后调用
add_category_to_loadable_list
函数将分类中的+load
方法存入loadable_categories
中
4.2 call_load_methods 分析
call_load_methods 源码:
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
call_load_methods
调用类和分类中的+load
方法,分别通过call_class_loads
函数和call_category_loads
函数,根据prepare_load_methods
函数添加的顺序,会优先调用父类的+load
方法。
- 通过
objc_autoreleasePoolPush
压栈一个自动释放池 - 开始一个
do while
循环,条件是loadable_classes_used > 0 || more_categories
- 遍历
loadable_classes
中的+load
方法进行调用,直到找不到位置 - 通过
call_category_loads
函数调用一次分类中的+load
方法 - 通过
objc_autoreleasePoolPop
出栈一个自动释放池
小结:
load_image主要是加载非懒加载的类和分类。
5. initialize
在开发过程中与+load
作用差不多的就是+initialize
,都是我们用来提前初始化一些数据方法。在上面我们知道了+load
方法的调用时机,那么+initialize
在哪里被调用的呢?
我们实现一个LGPerson
的类,代码如下:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@end
NS_ASSUME_NONNULL_END
#import "LGPerson.h"
@implementation LGPerson
+ (void)load{
NSLog(@"%s",__func__);
}
+ (void)initialize
{
NSLog(@"%s",__func__);
}
@end
我们在main
函数中分别调用LGPerson
的alloc
方法和class
方法:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc];
// Class cls = [LGPerson class];
}
return 0;
}
无论调用alloc
还是class
打印结果都是如下:
所以说我们的initialize
的调用并不在alloc
或者class
中,这个时候我们想initialize
的调用很大概率是在发送消息的时候,因为alloc
和class
都是方法,方法的本质就是消息的发送,iOS Objective-C 消息的查找所以我们来到lookUpImpOrForward
,翻阅后找到如下代码,并设置如下断点。
当先来到mian
中我们调用LGPerson
的断点时,在开启此断点,以便确定确实我们研究的是LGPerson
类。断点会来两次,第一次是查找alloc
方法,第二次是查找initialize
方法,调用堆栈如下:
当我们过掉第二次断点后就打印了initialize
方法内的内容,并且我们可以通过调用堆栈验证我们的initialize
是由initializeAndLeaveLocked
->initializeAndMaybeRelock
->initializeNonMetaClass
调起的。我们点击调用堆栈中的initializeNonMetaClass
就可以直接找到调用initialize
方法的地方,callInitialize
如此的完美简单明了。
callInitialize 源码:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
在callInitialize
源码中我们也可以清晰的看到直接通过消息的发送调起了initialize
小结:
至此我们的initialize
的调用就很清楚了,在我们第一次发送消息的时候就会调用initialize
方法,这个就比+load
方法好多了,类似于懒加载,用到类的时候在调用,而不像+load
启动就会被调用。这也是Apple推荐的使用方式。尽量不用+load
,使用initialize
替代它。
6.相关知识点延伸
6.1 懒加载类的加载
首先我们来到lookUpImpOrForward
函数,我们会看到这样一行代码:
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
当第一次发送消息的时候,就会实现该类及其原类:
realizeClassMaybeSwiftAndLeaveLocked源码:
static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
realizeClassMaybeSwiftMaybeRelock源码:
/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class.
* Locking:
* runtimeLock must be held on entry
* runtimeLock may be dropped during execution
* ...AndUnlock function leaves runtimeLock unlocked on exit
* ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
realizeClassWithoutSwift(cls, nil);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
ASSERT(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
通过realizeClassMaybeSwiftMaybeRelock
函数我们可以知道,当不是Swift
的类会调用realizeClassWithoutSwift
函数,这就跟我们在3.10的时候介绍的一模一样了。
initializeAndLeaveLocked 源码:
// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
initializeAndMaybeRelock 源码:
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
* inst is an instance of cls, or nil. Non-nil is better for performance.
* Returns the class pointer. If the class was unrealized then
* it may be reallocated.
* Locking:
* runtimeLock must be held by the caller
* This function may drop the lock.
* On exit the lock is re-acquired or dropped as requested by leaveLocked.
**********************************************************************/
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
ASSERT(cls->isRealized());
if (cls->isInitialized()) {
if (!leaveLocked) lock.unlock();
return cls;
}
// Find the non-meta class for cls, if it is not already one.
// The +initialize message is sent to the non-meta class object.
Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// Realize the non-meta class if necessary.
if (nonmeta->isRealized()) {
// nonmeta is cls, which was already realized
// OR nonmeta is distinct, but is already realized
// - nothing else to do
lock.unlock();
} else {
nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
// runtimeLock is now unlocked
// fixme Swift can't relocate the class today,
// but someday it will:
cls = object_getClass(nonmeta);
}
// runtimeLock is now unlocked, for +initialize dispatch
ASSERT(nonmeta->isRealized());
initializeNonMetaClass(nonmeta);
if (leaveLocked) runtimeLock.lock();
return cls;
}
initializeAndMaybeRelock
首先通过getMaybeUnrealizedNonMetaClass
函数在cls
和inst
中找出一个非元类,通过判断非元类有没有被实现,如果实现了就返回了,如果没实现则调用realizeClassMaybeSwiftAndUnlock
进行实现处理,这就跟上面一样了
结论:对于懒加载的类,在第一次发送消息的时候进行实现,会同时实现它的元类和其本身
验证以上结论:
我们创建一个LGPerson
类,实现sayClassFunc
类方法和saySomething
对象方法,添加一个testName
属性。调用代码如下图,并设置断点:
来到上面断点后在lookUpImpOrForward
函数中添加如下断点:
过掉main
函数中的断点后,来到上图所示断点,我们通过lldb
进行打印,查看我们的类信息。
此时我们可以看到我们的rw
的methods
的list地址是0x00,说明懒加载类并没有初始化,如果初始化了应该是个实际的地址,其实ro
里面也没有值,我们过掉这行代码再次进行lldb
查看:
此时我们看到了sayClassFunc
,说明cls
是元类,这里对元类进行了初始化。
下面我们来看看类:
如上图箭头所示,我们的rw
中并没有方法和属性。并且根据内容可以看出,这些都是野指针,虽然不是000,但也不是真实数据,其实ro
里面也没有值,这里就不上图了。我们过掉断点再次查看:
经过上面两幅图我们可以看到,过掉断点后ro
和rw
都已经有值了,说明该处确实完成了懒加载类的初始化。
注!但是有一点需要注意,如果该类作为非懒加载类的父类,同样会在非懒加载类初始化的时候进行初始化操作。就是不会在发送消息的时候进行初始化了。即使该类是懒加载类。当该类有个非懒加载的分类也会提前加载,懒加载正所谓用到才加载,所以说对于懒加载类的加载并不止上面介绍的这一种加载方式。