iOS 底层探索 文章汇总
目录
- 一、前言
- 二、_objc_init方法分析
- 三、_dyld_objc_notify_register方法分析
- 四、map_images 方法分析
- 五、_read_images 方法分析
- 六、readClass 方法分析
一、前言
上一篇文章iOS dyld流程分析中我们分析了dyld
流程,知道了dyld
会将库和代码
编译加载到内存中。然后通过读取macho
文件中的data
获取到类信息,其中data
中包含了ro、rw、rwe
。后面的学习中我们将探索方法、属性、协议
什么时候添加到类中的,rwe
什么时候产生的?那么这篇文章我们就先分析dyld
和objc
的关联关系。
二、_objc_init方法分析
dyld
加载库和代码后会进行objc
的初始化,调用的方法为:_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();
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()
方法为运行时环境的初始化,打印出所有运行时环境变量
运行代码打印结果如下:
由此可知设置环境变量OBJC_PRINT_LOAD_METHODS
即可打印+load
方法
所有调用了+load
方法的类均会打印出来:
_objc_init
中调用的各个方法说明
-
environ_init()
:读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助。 -
tls_init()
:关于线程key
的绑定-比如每条线程数据的析构函数。 -
static_init()
:运行C++
静态构造函数。在dyld
调用我们的静态构造函数之前,libc
会调用_objc_init()
,因此我们必须自己做。 -
runtime_init()
:runtime
运行时环境初始化,里面主要是:unattachedCategories
,allocatedClasses
后面会分析 -
exception_init()
:初始化libobjc
的异常处理系统 -
cache_init()
:缓存条件初始化 -
_imp_implementationWithBlock_init()
:启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
。 -
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
:dyld
注册的地方。
其中的重点就是
map_images
方法和load_images
方法的调用
三、_dyld_objc_notify_register方法分析
后续加载流程为:
然而_dyld_objc_notify_register
的调用在objc
源码中,_dyld_objc_notify_register
的实现却在dyld
源码中,因此这里存在跨库调用。
_dyld_objc_notify_register
实现如下:
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);
}
load_images
赋值给sNotifyObjCInit
这里找到load_images
方法的调用
同理也找到map_images
方法的调用
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
...
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
...
}
map_images
方法先调用,load_images
方法后调用
四、map_images 方法分析
map_images
方法的实现在objc
源码中
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);
}
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_image
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
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/53119145
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) {...}
}
_read_images
方法功能如下:
- 条件控制进行一次的加载
- 修复预编译阶段的
@selector
混乱问题 - 错误混乱的类处理
- 修复重映射一些没有被镜像文件加载进来的类
- 修复一些消息!
- 当我们类里面有协议的时候:
readProtocol
- 修复没有被加载的协议
- 分类处理
- 类的加载处理:实现
非懒加载
类(实现了+load方法的类和静态实例) - 没有被处理的类 优化哪些被侵犯的类
五、_read_images 方法分析
在_read_images
方法中我们发现这样的代码:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
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;
}
}
...
readClass
方法的作用:
通过
lldb
断点调试我们发现从macho
文件读取的cls
经过readClass
方法后cls
绑定了类
的信息。
六、readClass 方法分析
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName();
if (missingWeakSuperclass(cls)) {... }
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (Class newCls = popFutureNamedClass(mangledName)) {...}
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方法中如下代码就将类的信息从macho
中读取到了内存中,并加入类的实体表中。但此时内存中仅有类的地址和名字,还没有ro、rw、rew等数据。
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);