iOS开发之深入理解runtimeRuntime源码iOS

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

2019-02-01  本文已影响9人  kyson老师

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

runtime logo

本文完整版详见笔者小专栏: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的共享缓存中有没有

对应的方法是:

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);
    }
};

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

有则直接设置已加载

对应的代码:

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中的使用。虽然代码量有点多,但思路应该很清晰了,希望带给大家一些启发。

上一篇 下一篇

猜你喜欢

热点阅读