上回书说到程序启动之前dyld的代码处理流程。主要分析了dyld-941.5的源码,本文分析下在此流程中objc4源码(818.2)的处理逻辑。 首先回顾下整体流程:
_objc_init解析
我们在上图可以看出,dyld在main函数之前(pre-main)会间接调用到objc的_objc_init
,其中使用_dyld_objc_notify_register
注册了3个方法,但在这之前还做了一些初始化的操作。
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准备,创建2张表
runtime_init();
//异常初始化
exception_init();
#if __OBJC2__
//缓存
cache_t::init();
#endif
//macos专有
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
environ_init
其中environ_init
是读取environment variables
的一些配置信息,environment variables
在Edit Scheme -> Run -> Argments -> Environment Variables
中配置。
设置相关信息可以打印一些信息。可以设置
OBJC_HELP
,启动程序后会打印所有可以设置的信息已经含义解释。
objc[17346]: OBJC_HELP: describe available environment variables
objc[17346]: OBJC_HELP is set
objc[17346]: OBJC_PRINT_OPTIONS: list which options are set
objc[17346]: OBJC_PRINT_OPTIONS is set
objc[17346]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[17346]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[17346]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[17346]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[17346]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[17346]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[17346]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[17346]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
/**
。。。省略中间的
*/
objc[17346]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[17346]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
汉语版注解如下
除此之外,
environ_init
里面还对其他工程配置进行读取,比如NSZombiesEnabled
(僵尸对象检测)。
tls_init
接下来是 tls_init
, tls
是Thread Local Store
的缩写,线程局部存储主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不需要锁来维护。详细可以看这里。
/***********************************************************************
* 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
}
这里其实是给线程添加了析构函数,线程销毁,会调用_objc_pthread_destroyspecific
方法。
static_init
接下来是static_init
:找到objc库中的所有初始化方法,遍历调用,注意:objc库中的静态构造函
数早于load方法
,load方法
早于我们自己的静态构造函数
。
/***********************************************************************
* 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;
//获取objc库里面所有的静态构造函数
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
//遍历调用他们
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
runtime_init
接下来是runtime_init:
void runtime_init(void)
{
//分类加载表
objc::unattachedCategories.init(32);
//类的加载表
objc::allocatedClasses.init();
}
这里面其实是初始化两张表,以备后边加载类使用,这里可以留意一下这两张表unattachedCategories
和allocatedClasses
,后面会再次提及到。
other
其他的初始化,由于不影响整个流程,所以在这一小节一带而过。
exception_init
:初始化异常捕捉相关
cache_t::init
: 初始化缓存相关
_imp_implementationWithBlock_init
: MacOS中,让dyld去加载libobjc-trampolines.dylib
这个库。
map_images解析
上一篇博文,我们追踪到了load_images
的调用时机,这次我们直接来到他调用的地方,先分析其传入的参数。
三个参数:
参数 | 含义 |
---|---|
count | images的数量 |
paths[] | 库的路径(数组) |
mhdrs[] | 库对应的mach-header 信息(数组) |
再看map_image
的源码:
可以看到是在线程安全的情况下,调用
map_images_nolock
函数。
/***********************************************************************
* map_images_nolock
* Process the given images which are being mapped in by dyld.
* All class registration and fixups are performed (or deferred pending
* discovery of missing superclasses etc), and +load methods are called.
*
* info[] is in bottom-up order i.e. libobjc will be earlier in the
* array than any library that links to libobjc.
*
* Locking: loadMethodLock(old) or runtimeLock(new) acquired by map_images.
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{ //是否是第一次加载
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
if (firstTime) {
preopt_init();
}
//如果添加OBJC_PRINT_IMAGES环境,打印镜像数量
if (PrintImages) {
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
}
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
//通过 初始化header_info,header_info是一个链表,目前使用链表的目的还未知,已经使用数组存储
//并且计算classed的数量
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
//如果是可执行文件(我们的代码生成的)MH_EXECUTE就是我们主工程的代码
if (mhdr->filetype == MH_EXECUTE) {
// Size some data structures based on main executable's size
#if __OBJC2__
// If dyld3 optimized the main executable, then there shouldn't
// be any selrefs needed in the dynamic map so we can just init
// to a 0 sized map
if ( !hi->hasPreoptimizedSelectors() ) {
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
}
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
#if SUPPORT_GC_COMPAT
// Halt if this is a GC app.
if (shouldRejectGCApp(hi)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"Objective-C garbage collection "
"is no longer supported.");
}
#endif
}
//把hi存储起来
hList[hCount++] = hi;
if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
hi->fname(),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "",
hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
hi->info()->optimizedByDyld()?" (preoptimized)":"");
}
}
}
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) {
// 初始化函数注册表
sel_init(selrefCount);
// 初始化操作 3件事
// 1.自动释放池 AutoreleasePoolPage的初始化
// 2.SideTablesMap初始化
// 3.全局关联对象表的初始化
arr_init();
#if SUPPORT_GC_COMPAT
// Reject any GC images linked to the main executable.
// We already rejected the app itself above.
// Images loaded after launch will be rejected by dyld.
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"%s requires Objective-C garbage collection "
"which is no longer supported.", hi->fname());
}
}
#endif
#if TARGET_OS_OSX
// Disable +initialize fork safety if the app is too old (< 10.13).
// Disable +initialize fork safety if the app has a
// __DATA,__objc_fork_ok section.
// if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
// DisableInitializeForkSafety = true;
// if (PrintInitializing) {
// _objc_inform("INITIALIZE: disabling +initialize fork "
// "safety enforcement because the app is "
// "too old.)");
// }
// }
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
DisableInitializeForkSafety = true;
if (PrintInitializing) {
_objc_inform("INITIALIZE: disabling +initialize fork "
"safety enforcement because the app has "
"a __DATA,__objc_fork_ok section");
}
}
break; // assume only one MH_EXECUTE image
}
#endif
}
//⚠️最关键操作
if (hCount > 0) {
// 读取映射
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// Call image load funcs after everything is set up.
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
}
可以看到,该方法进行了一些初始化操作,并且遍历了所有的动态库的mach-header
,生成相应的header_info
并且放进hList
数组中,于此同时计算出class的总数totalClasses
。最重要的还是调用了_read_images
读取所有的动态库信息。
这里可以稍微注意一下,从while里面的i--
我们可以得知hList
的顺序是mhdrs
里面的倒叙。
进一步阅读_read_images
的源码,发现非常长,需要慢慢啃~这里我们分段讲解:
准备工作
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked
* list beginning with headerList.
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_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;
//环境变量设置 OBJC_PRINT_IMAGE_TIMES = YES 会打印ts.log的内容
TimeLogger ts(PrintImageTimes);
// 加锁
runtimeLock.assertLocked();
这里是初始化一些局部变量,注意OBJC_PRINT_IMAGE_TIMES
的设置可以使之后的ts.log()
生效。我们这分段也是通过ts.log()
来进行分段的。
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app or a framework contains Swift code "
"older than Swift 3.0");
}
break;
}
}
# endif
# if TARGET_OS_OSX
//... 不影响整体理解,已删除
# endif
#endif
// TaggedPoint的一些初始化
if (DisableTaggedPointers) {
disableTaggedPointers();
}
initializeTaggedPointerObfuscator();
// OBJC_PRINT_CLASS_SETUP设置,可以答应class数量(系统+自定义)
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// 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 是一张全局的哈希表
// 实际上它存放的是除了 dyld shared cache 中的 class,无论该 class 是否 realized。
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
注意这部分代码,通过静态变量doneOnce
控制,只会执行一次。其中创建的gdb_objc_realized_classes
这个表,后期会在addNamedClass
静态方法内部对表进行赋值。
SEL注册到nameSelectors表,并修复函数指针
// Fix up @selector references
// Note this has to be before anyone uses a method list, as relative method
// lists point to selRefs, and assume they are already fixed up (uniqued).
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
//取出__objc_selrefs的函数
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
//注册到函数表
SEL sel = sel_registerNameNoLock(name, isBundle);
//进行修正(fix up)如果地址发生变化,使用函数表中的地址
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
ts.log("IMAGE TIMES: fix up selector references");
把方法selector从__objc_selrefs
section
加载到map_images_nolock
中初始化的函数表中,并且进行修正。这里的修正我认为是一种rebase操作(虚拟内存映射物理内存,Aslr相关)。
注册类、重映射类
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
// 获取所有的类
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
//⚠️:重点
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
ts.log("IMAGE TIMES: discover classes");
这里的log输出是discover classes
,不难看出最重要的是其中的readClass
,我们继续看readClass
的实现。
其中addClassTableEntry
的操作是,把类添加到allocatedClasses
这个表中,该表在runtime_init
的时候初始化。
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
//重映射类
remapClassRef(&classrefs[i]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
ts.log("IMAGE TIMES: remap classes");
部分消息重映射
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
这部分为适配低版本,可以直接忽略。
将protocols添加到protocols_map中,并重映射
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
// Skip reading protocols if this is an image from the shared cache
// and we support roots
// Note, after launch we do need to walk the protocol as the protocol
// in the shared cache is marked with isCanonical() and that may not
// be true if some non-shared cache binary was chosen as the canonical
// definition
if (launchTime && isPreoptimized) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
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) {
// At launch time, we know preoptimized image refs are pointing at the
// shared cache definition of a protocol. We can skip the check on
// launch, but have to visit @protocol refs for shared cache images
// loaded later.
if (launchTime && hi->isPreoptimized())
continue;
// 获取所有的协议
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
//重映射
remapProtocolRef(&protolist[i]);
}
}
ts.log("IMAGE TIMES: fix up @protocol references");
加载协议,并修成协议,rebase。
加载分类
// 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/53119145
//全局变量,load_images 的时候设置为YES
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
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()
这里需要注意:didInitialAttachCategories
只会在load_images
中设置为true
,也就是说只有执行过load_images
,这里才会遍历load_categories_nolock
初始化非懒加载类
// 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;
//把类放进allocatedClasses 这个表中,该表在runtime_init的时候初始化
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");
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
//加载类(非swift类)
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
ts.log("IMAGE TIMES: realize future classes");
其中realizeClassWithoutSwift的实现中,对rw进行了操作。
其他打印操作
// OBJC_DEBUG_NONFRAGILE_IVARS 设置
if (DebugNonFragileIvars) {
realizeAllClasses();
}
// Print preoptimization statistics
// OBJC_PRINT_PREOPTIMIZATION 设置YES打印
if (PrintPreopt) {
static unsigned int PreoptTotalMethodLists;
static unsigned int PreoptOptimizedMethodLists;
static unsigned int PreoptTotalClasses;
static unsigned int PreoptOptimizedClasses;
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) {
_objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
"in %s", hi->fname());
}
else if (hi->info()->optimizedByDyld()) {
_objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
"in %s", hi->fname());
}
classref_t const *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
PreoptTotalClasses++;
if (hi->hasPreoptimizedClasses()) {
PreoptOptimizedClasses++;
}
const method_list_t *mlist;
if ((mlist = cls->bits.safe_ro()->baseMethods)) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
if ((mlist = cls->ISA()->bits.safe_ro()->baseMethods)) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
}
}
_objc_inform("PREOPTIMIZATION: %zu selector references not "
"pre-optimized", UnfixedSelectors);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
PreoptOptimizedMethodLists, PreoptTotalMethodLists,
PreoptTotalMethodLists
? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists
: 0.0);
_objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
PreoptOptimizedClasses, PreoptTotalClasses,
PreoptTotalClasses
? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
: 0.0);
_objc_inform("PREOPTIMIZATION: %zu protocol references not "
"pre-optimized", UnfixedProtocolReferences);
}
#undef EACH_HEADER
}
这里是一些其他的打印信息。可以设置环境变量,打印辅助debug。
load_images解析
load_images
方法源码:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
// 加载所有的分类
loadAllCategories();
}
// 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
{
// 找到load方法
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
//调用load方法
call_load_methods();
}
加载分类
其中loadAllCategories
里面是调用的load_categories_nolock
:
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
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) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
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 {
// runtime_init的时候创建,第一部分有讲到
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
将所有分类添加到runtime_init
中初始化的unattachedCategories
表中。
调用load方法准备
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
//swift没有load方法
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);
}
从所有的非懒加载类、非懒加载分类中找出load
方法。
调用load方法
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_class_loads
:
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
//⚠️直接函数调用,没有msg_send
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
通过这些分析可以总结出load方法一些特性:
- 调用顺序为父类 > 本类 > 分类
- 调用方式为直接地址调用
- 线程安全
- 调用时机优先与main函数
写在最后
其实objc在程序启动的时候做了很多重要的操作,本篇篇幅和时间有限,不能将所有内容都详细解释,例如map_iamges的每一个小点,都可以一层一层深入阅读,里面操作对程序整理理解非常有用。有感兴趣的读者可以慢慢探究,也可以留言交流。本篇源码较多,所有objc4都是基于目前最新的818.2版本。