iOS开发之runtime(18):header_info详解(1)

logo

本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

分析

上一篇文章我们说过header_info封装了headerType,后者前面的文章已经说过了,其实是mach_header_64类型,封装函数如下:

auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);

其实现如下(去掉部分冗余逻辑):

static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
{
    header_info *hi;
    if (bad_magic(mhdr)) return NULL;
    bool inSharedCache = false;
    // Look for hinfo from the dyld shared cache.
    hi = preoptimizedHinfoForHeader(mhdr);
    if (hi) {
        // Found an hinfo in the dyld shared cache.
        // Weed out duplicates.
        if (hi->isLoaded()) {
            return NULL;
        }
        inSharedCache = true;
        // Initialize fields not set by the shared cache
        // hi->next is set by appendHeader
        hi->setLoaded(true);
    }
    else 
    {
        // Weed out duplicates
        for (hi = FirstHeader; hi; hi = hi->getNext()) {
            if (mhdr == hi->mhdr()) return NULL;
        }
        // Locate the __OBJC segment
        size_t info_size = 0;
        unsigned long seg_size;
        const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
        const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
        if (!objc_segment  &&  !image_info) return NULL;

        // Allocate a header_info entry.
        // Note we also allocate space for a single header_info_rw in the
        // rw_data[] inside header_info.
        hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);

        // Set up the new header_info entry.
        hi->setmhdr(mhdr);
        // Install a placeholder image_info if absent to simplify code elsewhere
        static const objc_image_info emptyInfo = {0, 0};
        hi->setinfo(image_info ?: &emptyInfo);

        hi->setLoaded(true);
        hi->setAllClassesRealized(NO);
    }

    {
        size_t count = 0;
        if (_getObjc2ClassList(hi, &count)) {
            totalClasses += (int)count;
            if (!inSharedCache) unoptimizedTotalClasses += count;
        }
    }
    appendHeader(hi);
    return hi;
}

这个函数其实很好理解:

  • 判断一下当前的header在dyld的共享缓存中有没有
  • 如果有的话直接设置已加载
  • 如果共享缓存中没有,那么就实行“封装操作”
  • 封装成功以后,加入到链表中。
    现在我们一步步分析:

判断当前的header在dyld的共享缓存中有没有

对应的方法是:

hi = preoptimizedHinfoForHeader(mhdr);

其实现如下(去掉部分冗余逻辑):

header_info *preoptimizedHinfoForHeader(const headerType *mhdr)
{
    objc_headeropt_ro_t *hinfos = opt ? opt->headeropt_ro() : nil;
    if (hinfos) return hinfos->get(mhdr);
    else return nil;
}

不难看出,这个共享缓存的数据都在opt内,我们推测opt应该是一个全局或者静态变量,点击进入看一下其声明以及定义:

// preopt: the actual opt used at runtime (nil or &_objc_opt_data)
// _objc_opt_data: opt data possibly written by dyld
// opt is initialized to ~0 to detect incorrect use before preopt_init()
static const objc_opt_t *opt = (objc_opt_t *)~0;

果然是个静态变量。
~0这个之前笔者已经介绍过了,其实就是0Xffffffff,这是块安全区域,防止进入其他位置导致野指针。上面的注释也大概介绍了opt初始化时机:方法preopt_init()中。
那调用时机是在哪里呢,见下图:

preopt_init()调动时机

该方法的实现这里笔者就不展开讲了,有兴趣的读者可以自行参阅。

opt的定义也不复杂,代码拷贝如下:

struct alignas(alignof(void*)) objc_opt_t {
    uint32_t version;
    uint32_t flags;
    int32_t selopt_offset;
    int32_t headeropt_ro_offset;
    int32_t clsopt_offset;
    int32_t protocolopt_offset;
    int32_t headeropt_rw_offset;

    const objc_selopt_t* selopt() const {
        if (selopt_offset == 0) return NULL;
        return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
    }
    objc_selopt_t* selopt() { 
        if (selopt_offset == 0) return NULL;
        return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
    }

    struct objc_headeropt_ro_t* headeropt_ro() const {
        if (headeropt_ro_offset == 0) return NULL;
        return (struct objc_headeropt_ro_t *)((uint8_t *)this + headeropt_ro_offset);
    }

    struct objc_clsopt_t* clsopt() const { 
        if (clsopt_offset == 0) return NULL;
        return (objc_clsopt_t *)((uint8_t *)this + clsopt_offset);
    }

    struct objc_protocolopt_t* protocolopt() const { 
        if (protocolopt_offset == 0) return NULL;
        return (objc_protocolopt_t *)((uint8_t *)this + protocolopt_offset);
    }

    struct objc_headeropt_rw_t* headeropt_rw() const {
        if (headeropt_rw_offset == 0) return NULL;
        return (struct objc_headeropt_rw_t *)((uint8_t *)this + headeropt_rw_offset);
    }
};

看名字就很容易理解,分别是

  • 类的缓存clsopt()
  • 协议的缓存protocolopt()
  • 头的缓存headeropt_rw()
  • 选择器的缓存selopt()

有则直接设置已加载

对应的代码:

if (hi) {
        // Found an hinfo in the dyld shared cache.
        // Weed out duplicates.
        if (hi->isLoaded()) {
            return NULL;
        }
        inSharedCache = true;
        // Initialize fields not set by the shared cache
        // hi->next is set by appendHeader
        hi->setLoaded(true);
    }

对,很好理解,唯一有问题的点在于方法isLoaded()

bool isLoaded() {
    return getHeaderInfoRW()->getLoaded();
}

继续进入该方法:

header_info_rw *getHeaderInfoRW() {
    header_info_rw *preopt =
        isPreoptimized() ? getPreoptimizedHeaderRW(this) : nil;
    if (preopt) return preopt;
    else return &rw_data[0];
}

我们查看isPreoptimized()可以发现:


/***********************************************************************
* Return YES if we have a valid optimized shared cache.
**********************************************************************/
bool isPreoptimized(void) 
{
    return preoptimized;
}

preoptimized定义如下:

static bool preoptimized;

这也是个静态变量。和前面的opt一样!也就是说dlyd的共享缓存其实是由两个变量来决定的一个opt存放动态缓存数据,另一个preoptimized存放标志位。

如果共享缓存中没有,那么就实行“封装操作”

这一步操作有点复杂,这里先不做介绍了,后面的文章着重分析。

封装成功以后,加入到链表中

appendHeader(hi);

其具体实现如下:

/***********************************************************************
* appendHeader.  Add a newly-constructed header_info to the list. 
**********************************************************************/
void appendHeader(header_info *hi)
{
    // Add the header to the header list. 
    // The header is appended to the list, to preserve the bottom-up order.
    HeaderCount++;
    hi->setNext(NULL);
    if (!FirstHeader) {
        // list is empty
        FirstHeader = LastHeader = hi;
    } else {
        if (!LastHeader) {
            // list is not empty, but LastHeader is invalid - recompute it
            LastHeader = FirstHeader;
            while (LastHeader->getNext()) LastHeader = LastHeader->getNext();
        }
        // LastHeader is now valid
        LastHeader->setNext(hi);
        LastHeader = hi;
    }
}

从以上代码看出header_info原来还是个链表:

//获取下一个data:
header_info *getNext() {
    return getHeaderInfoRW()->getNext();
}
//设置下一个数据
void setNext(header_info *v) {
    getHeaderInfoRW()->setNext(v);
}

怎么样,读者朋友们是不是对header_info有了更深的了解。

总结

本文主要介绍了方法addheader,调用栈位于:

_objc_init
|-dyld_objc_notify_register
  |-map_2_images
    |-map_images_nolock
      |-addHeader

当然,本文也顺便带出了dyld的共享缓存在runtime中的使用。虽然代码量有点多,但思路应该很清晰了,希望带给大家一些启发。

广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

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

推荐阅读更多精彩内容