上一篇我们主要探索了dyld的链接加载,本篇开始我们探索运行时类的加载过程,本篇只是引子。
_objc_init
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();
//关于线程key的绑定
tls_init();
//运行C ++静态构造函数
static_init();
//runtime运行时环境初始化
runtime_init();
//初始化libobjc的异常处理系统
exception_init();
#if __OBJC2__
//缓存条件初始化
cache_t::init();
#endif
//启动回调机制
_imp_implementationWithBlock_init();
//dyld 注册的地方
//map_images:dyld将image镜像文件加载进内存时,会触发该函数
//load_images:dyld初始化image会触发该函数
//unmap_image:dyld将image移除时会触发该函数
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
我们可以看到,_objc_init主要是执行一些初始化方法,包括
environ_init():读取影响运行时的环境变量,如果需要可以打印环境变量提供帮助。tls_init():关于线程key的绑定,例如每线程数据的析构函数。static_init():运行C++静态构造函数。runtime_init():runtime运行时环境的初始化,后面我们详细分析。exception_init():初始化libobjc的异常处理系统。cache_t::init():缓存条件初始化。_imp_implementationWithBlock_init():启动回调机制。_dyld_objc_notify_register:dyld的注册。
environ_init 环境变量初始化
environ_init的源码如下:
void environ_init(void)
{
///省略代码
// 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);
}
}
}
可以看出来就是一些环境变量的初始化,参看这段代码,我们可以打印环境变量。
-
将条件去掉直接调用
for循环for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) { const option_t *opt = &Settings[i]; _objc_inform("%s: %s", opt->env, opt->help); _objc_inform("%s is set", opt->env); } -
通过终端命令
export OBJC_hrlp = 1,打印环境变量
我们平时项目中可能会有几个环境变量可以在我们的xcode配置(target -- Edit Scheme -- Run --Arguments -- Environment Variables)一下对应的值,达到修改环境变量的目的:
DYLD_PRINT_STATISTICS:设置DYLD_PRINT_STATISTICS为YES,控制台就会打印 App 的加载时长,包括整体加载时长和动态库加载时长,即main函数之前的启动时间(查看pre-main耗时),可以通过设置了解其耗时部分,这个我们做启动优化会用到。OBJC_DISABLE_NONPOINTER_ISA:不使用nonpointer isa(nonpointer isa指针地址末尾为1),生成的都是普通的isa,这个我们项目里一般不会改,探索源码的时候可以尝试查看两种isa结构的区别。OBJC_PRINT_LOAD_METHODS:打印Class及Category的+ (void)load方法的调用信息,启动优化也可以参考,因为load方法过多也会使启动变慢。
tls_init:线程key的绑定
主要作用是本地线程池的初始化以及析构,源码:
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
}
static_init 运行系统级别的C++静态构造函数
主要是运行系统级别的C++静态构造函数,在dyld调用我们的静态构造函数之前,libc调用_objc_init方法,即系统级别的C++构造函数 先于 自定义的C++构造函数 运行
static void static_init()
{
size_t count;
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 运行时环境初始化
主要是运行时的初始化,主要分为两部分:分类初始化、类的表初始化,我们下一篇会详细探索。
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
exception_init 异常初始化
主要是初始化libobjc的异常处理系统,注册异常处理的回调,从而监控异常的处理
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
_objc_terminate的实现:
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)();
}
}
}
当crash的时候会执行_objc_terminate方法,最后会执行uncaught_handler抛出异常。
uncaught_handler:-
objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn) { // fn为设置的异常句柄 传入的函数,为外界给的 objc_uncaught_exception_handler result = uncaught_handler; uncaught_handler = fn; //赋值 return result; } - 主要用来处理异常,在我们的App中可以添加一个异常句柄
NSSetUncaughtExceptionHandler来处理异常。
cache_init:缓存初始化
主要就是缓存初始化
void cache_t::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
}
_imp_implementationWithBlock_init:启动回调机制
该方法主要是启动回调机制,看代码iOS端没做任何处理。
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
}
_dyld_objc_notify_register:dyld注册
这个方法我们上一篇有提到过,主要是
-
仅供
objc运行时使用 -
注册处理程序,以便在映射、取消映射和初始化objc图像时调用 -
dyld将会通过一个包含objc-image-info的镜像文件的数组回调mapped函数
三个参数的作用:
map_images:dyld将image(镜像文件)加载进内存时,会触发该函数
load_image:dyld初始化image会触发该函数
unmap_image:dyld将image移除时,会触发该函数
我们_objc_init调用的方法有了初步认识,接下来我们开始探索类的加载。
_read_images
_read_images的引入
_dyld_objc_notify_register第一个参数是&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比较简单,调用map_images_nolock,我们继续探索。
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();
}
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];
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
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
}
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);
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]);
}
}
}
map_images_nolock方法比较长,很大一部分代码是读取镜像的准备,核心调用是_read_images方法。
_read_images分析
_read_images有三百多行代码,一行行读容易迷失,我们先将方法内的{}语句隐藏,从全局看这个方法都做了什么:
这个方法有个特点就是没执行完一个代码块都会执行
ts.log()函数打印代码段都做了什么,这样子我们大体知道_read_images的主流程:
- 条件控制进⾏⼀次的加载
- 修复预编译阶段的 @selector 的混乱问题
- 错误混乱的类处理
- 修复重映射⼀些没有被镜像⽂件加载进来的类
- 修复⼀些消息
- 当我们类⾥⾯有协议的时候 : readProtocol
- 修复没有被加载的协议
- 分类处理
- 类的加载处理
- 没有被处理的类 优化那些被侵犯的类
first time tasks即:条件控制进⾏⼀次的加载
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
// Disable non-pointer isa if the app is too old
// (linked before OS X 10.11)
// if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
// DisableNonpointerIsa = true;
// if (PrintRawIsa) {
// _objc_inform("RAW ISA: disabling non-pointer isa because "
// "the app is too old.");
// }
// }
// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
// New apps that load old extensions may need this.
for (EACH_HEADER) {
if (hi->mhdr()->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app has a __DATA,__objc_rawisa section");
}
}
break; // assume only one MH_EXECUTE image
}
# endif
#endif
if (DisableTaggedPointers) {
disableTaggedPointers();
}
///初始化TaggedPointer混淆
initializeTaggedPointerObfuscator();
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
// objc::unattachedCategories.init(32);
// objc::allocatedClasses.init();
//负载因子
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//创建一张类的总表,包含所有的类
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
- 初始化小对象(
TaggedPointer)
- 创建所有类的总表,注意这个表和
runtime_init中allocatedClassess的表不一样,这里是所有类的表,而allocatedClassess是已经实现(allocated)的类的表。
fix up selector references即:修复预编译阶段的 @selector 的混乱问题
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->hasPreoptimizedSelectors()) continue;
bool isBundle = hi->isBundle();
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);
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
ts.log("IMAGE TIMES: fix up selector references");
selecotr是类的名字+地址。
SEL *sels = _getObjc2SelectorRefs(hi, &count);是从Mach-o文件读取的SEL sel = sel_registerNameNoLock(name, isBundle);是从dyld链接之后获取的- 如果两个
sel名字相等,地址不同就需要fix up。
discover classes即:错误混乱的类处理
// 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;
}
//从mach-o读取类
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
//cls 目前没有名字
Class cls = (Class)classlist[i];
//关联类cls的名字
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");
修复未处理的将来的类。给类关联上名字。下面我们重点探索一下readClass:
readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
///以下代码 为笔者添加 方便调试到自定义的类(因为所以类都会执行这里,我们只关心我们定义的类)
const char *mangledName = cls->nonlazyMangledName();
const char *customerClassName = "JSPerson";
if (strcmp(mangledName, customerClassName) == 0) {
//打印类名
printf("%s -: 要研究的类: - %s\n",__func__,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->setSuperclass(nil);
return nil;
}
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (mangledName != nullptr) {
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));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
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(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
///给类添加名字
addNamedClass(cls, mangledName, replacing);
} else {
///元类也处理
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
///添加到class表
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;
}
我们从返回值开始看cls,cls也是入参,经过readClass之后有了名字,主要作用就是对类及元类赋值名字并放入方法表中。
总结
本篇我们主要是对objc_init的流程做了一个简单的梳理,分析了readClass方法的作用,下一篇我们开始详细分析类的加载过程。