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壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容