iOS开发之runtime(17):_dyld_objc_notify_register方法介绍

logo

本系列博客是本人的源码阅读笔记,如果有 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_headergetsectiondata中会用到,后面我们看到,第一步即是对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壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容