美文网首页iOS开发之深入理解runtimeRuntime源码iOS
iOS开发之runtime(18):header_info详解

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

作者: kyson老师 | 来源:发表于2019-02-01 08:08 被阅读9次

    本系列博客是本人的源码阅读笔记,如果有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的共享缓存中有没有
    • 如果有的话直接设置已加载
    • 如果共享缓存中没有,那么就实行“封装操作”
    • 封装成功以后,加入到链表中。
      现在我们一步步分析:

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

    相关文章

      网友评论

        本文标题:iOS开发之runtime(18):header_info详解

        本文链接:https://www.haomeiwen.com/subject/qmursqtx.html