本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
背景
本文笔者将像大家介绍runtime中最重要的方法:_dyld_objc_notify_register
。该方法是runtime的核心方法,可以说承载了runtime的大部分功能。今天我们就大概介绍一下这个方法吧。
分析
_dyld_objc_notify_register
有四个关键字,dlyd,objc,通知,注册。大概意思应该是,dyld通知了objc注册了。那事实是不是这样呢,我们看一下这个方法的注释:
//
// 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);
翻译过来,大概意思是:
该方法是runtime特有的方法。该方法的调用时机是,当oc对象、镜像(images)被映射(mapped),未被映射(unmapped),以及被初始化了(initialized)。这个方法是dlyd中声明的,一旦调用该方法,调用结果会作为该函数的参数回传回来。比如,当所有的images以及section为“objc-image-info”被加载之后会回调mapped方法。
load方法也将在这个方法中被调用。
关于该方法这里不多做介绍了。感兴趣的同学可以下载dyld库的源代码查看。
我们今天先大概说一下回调方法map_2_images
:
void
map_2_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
rwlock_writer_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[])
{
//该tag表明是否是第一次调用该方法。
static bool firstTime = YES;
//头信息列表
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
hCount = 0;
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_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
}
hList[hCount++] = hi;
}
}
if (firstTime) {
sel_init(selrefCount);
arr_init();
}
if (hCount > 0) {
//这里读取images
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
相信大家看了上面的代码已经笔者的注释还是感觉丈二和尚摸不着头脑。所以笔者想给大家总结一下,以上代码做了四件事
- 拿到dlyd传过来的header,进行封装
- 初始化selector
- 初始化 autorelease pool page
- 读取images
以上的每一步都又会分成很多小步骤,后面的文章会给大家一一解释,本文先介绍第一步:
这一步从函数:
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
开始。
这个函数的四个参数大概可以猜到:
-
mhCount
:mach-o header count,即mach-o header 个数 -
mhPaths
:mach-o header Paths,即header的路径数组 -
mhdrs
:mach-o headers,即headers
这几个参数中mhdrs
应该是我们比较属性的类型了,上篇文章中其实笔者就和大家讨论过mach_header
在getsectiondata
中会用到,后面我们看到,第一步即是对header进行操作,将其遍历并封装成了hi
对象:
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
//...这里省略后续代码
点击方法addHeader
,进入其中可以发现,其返回的是类型:header_info
:
static header_info * addHeader(const headerType *mhdr, const char *path,
int &totalClasses, int &unoptimizedTotalClasses)
因此我们确认了,这一步的操作是将
mach_header
转换为header_info
类型。接下来,我们只需要研究header_info
这个结构体即可。进入其定义:
typedef struct header_info {
private:
// Note, this is no longer a pointer, but instead an offset to a pointer
// from this location.
intptr_t mhdr_offset;
// Note, this is no longer a pointer, but instead an offset to a pointer
// from this location.
intptr_t info_offset;
// Do not add fields without editing ObjCModernAbstraction.hpp
public:
header_info_rw *getHeaderInfoRW() {
header_info_rw *preopt =
isPreoptimized() ? getPreoptimizedHeaderRW(this) : nil;
if (preopt) return preopt;
else return &rw_data[0];
}
const headerType *mhdr() const {
return (const headerType *)(((intptr_t)&mhdr_offset) + mhdr_offset);
}
void setmhdr(const headerType *mhdr) {
mhdr_offset = (intptr_t)mhdr - (intptr_t)&mhdr_offset;
}
const objc_image_info *info() const {
return (const objc_image_info *)(((intptr_t)&info_offset) + info_offset);
}
void setinfo(const objc_image_info *info) {
info_offset = (intptr_t)info - (intptr_t)&info_offset;
}
bool isLoaded() {
return getHeaderInfoRW()->getLoaded();
}
void setLoaded(bool v) {
getHeaderInfoRW()->setLoaded(v);
}
bool areAllClassesRealized() {
return getHeaderInfoRW()->getAllClassesRealized();
}
void setAllClassesRealized(bool v) {
getHeaderInfoRW()->setAllClassesRealized(v);
}
header_info *getNext() {
return getHeaderInfoRW()->getNext();
}
void setNext(header_info *v) {
getHeaderInfoRW()->setNext(v);
}
bool isBundle() {
return mhdr()->filetype == MH_BUNDLE;
}
const char *fname() const {
return dyld_image_path_containing_address(mhdr());
}
bool isPreoptimized() const;
#if !__OBJC2__
struct old_protocol **proto_refs;
struct objc_module *mod_ptr;
size_t mod_count;
# if TARGET_OS_WIN32
struct objc_module **modules;
size_t moduleCount;
struct old_protocol **protocols;
size_t protocolCount;
void *imageinfo;
size_t imageinfoBytes;
SEL *selrefs;
size_t selrefCount;
struct objc_class **clsrefs;
size_t clsrefCount;
TCHAR *moduleName;
# endif
#endif
private:
// Images in the shared cache will have an empty array here while those
// allocated at run time will allocate a single entry.
header_info_rw rw_data[];
} header_info;
对!代码量很大!我们这里先有个了解即可,对于header_info
的分析会在后面的文章中给出。
广告
我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。