美文网首页
iOS 的Category实现原理

iOS 的Category实现原理

作者: Jason_YZC | 来源:发表于2018-12-06 17:04 被阅读58次

Category 加载过程原理

  • 是通过runtime加载类的所有Category数据
  • Category的方法、属性、协议数据,合并到一个大数组中(后面参与编译的Category数据,会在数组前面)
  • 将合并后的分类数据(属性、方法、协议),插入到原来数据方法的前面

源码解读

15440831307747.jpg

上图就是runtime源码 category的入口,通过上面可以发现category其实是一个category_t的结构体,而且还是个二维数组。

  • 下面是category的主要处理方法
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    //分配内存
    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        //取出某个分类
        auto& entry = cats->list[i];

        //取出分类的中的对象方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    //得到内对象里面的数据
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    
    //将所有分类的对象方法,附加到类对象的方法列表中
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

 void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            
            //重新分配内存
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            
            //内存移动,也就是将原来的东西挪动到后面
            memmove(array()->lists + addedCount,
                    array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            
            //将addedLists 复制到 array()->lists, addedLists就是category的内容,
            memcpy(array()->lists,
                   addedLists,
                   addedCount * sizeof(array()->lists[0]));
        }
    //...没有全部源码拿出来,主要处理逻辑已经在上面了。

从上面源码已经可以看到category的整个处理过程,其实主要部分就是:

  • 先拿到category的所有方法、属性、协议;
  • 然后给原来的方法重新分配一块大的内存;
  • 接着把原来的内容移动到数据组的后面;
  • 再把category的东西复制到数组的前面去。

+laod方法

  • +load方法会在runtime加载类的、分类的时候调用
  • 每个类、分类的+load,在程序运行过程中只调用一次

源码解读
runtime load_image()函数里面,这个函数意思就是加载模块的意思

load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

其中可以先看下call_load_methods()里面的操作

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

其中从上面源码可以看出他是先调用类的load方法,那么是怎么调用类的load的呢

static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

其实就是直接拿到类的方法,然后用load_method_t存起来,而load_method_t 是一个指向方法的结构体,下面就是通过该指针直接调用该方法

通过上述源码可以总结如下几点:

  • 调用顺序

    1. 先调用类的+load
    * 先编译,先调用
    * 调用子类的`+load`方法之前,先调用父类的`+load`
    
    1. 再调用分类的+load
      • 先编译,先调用原则

+initialize方法

+initialize方法会在类第一次接收到消息时调用

  • 调用顺序

    • 先调用父类的+initialize,再调用子类的+initialize
    • (先初始化父类,再初始化子类,每个类只会初始化1次)
  • +initialize+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点:

    • 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    • 如果分类实现了+initialize,就覆盖类本身的+initialize调用

load、initialize方法的区别什么?
1.调用方式

  • load是根据函数地址直接调用
  • initialize是通过objc_msgSend调用

2.调用时刻

  • load是runtime加载类、分类的时候调用(只会调用1次)
  • initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

load、initialize的调用顺序?
1.load
1> 先调用类的load

  • 先编译,先调用
  • 调用子类的load之前,会先调用父类的load

2> 再调用分类的load

  • 先编译,先调用

2.initialize

  • 先初始化父类
  • 再初始化子类(可能最终调用的是父类的initialize方法)

相关文章

网友评论

      本文标题:iOS 的Category实现原理

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