Cateogry

作者: comsubin | 来源:发表于2019-05-23 15:54 被阅读0次

底层结构

新建一个类Person,分类Person+test,用之前学过的命令

$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+test.m -o person+test.c++

当程序编译的时候.会生成这样一个_category_t结构体


struct _category_t {
    const char *name;//类名
    struct _class_t *cls;//0
    const struct _method_list_t *instance_methods;//实例方法列表
    const struct _method_list_t *class_methods;//类方法列表
    const struct _protocol_list_t *protocols;//协议列表
    const struct _prop_list_t *properties;//属性列表
};


extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_test_test}}
};

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"age","Ti,N"}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;

//赋值给_category_t
static struct _category_t _OBJC_$_CATEGORY_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_test,
    0,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_test,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_test(void ) {
    _OBJC_$_CATEGORY_Person_$_test.cls = &OBJC_CLASS_$_Person;
}

源码分析

objc-os.mm---->_objc_init---->map_image---->map_images_nolock--->_read_images

 // Discover categories. 
    for (EACH_HEADER) {
//二维数组
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

这块核心方法remethodizeClass(重组方法),这里参数是classormeta-class .最终来到attachCategories

attachCategories(cls, cats, true /*flush caches*/);   

核心实现

static void 
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_t,,method_t]]
    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;
            //array()->lists 原来的方法列表 往后移动addedCount
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            //addedLists填充到前面
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
  • 通过runtime把 所有category的类方法,属性,协议各自合并为一个大数组.
  • 将合并后的分类数据,通过attachLists方法插入到类原来的数据的前面.(所以同样的方法会优先调用分类,并不是覆盖)

总结: category的实现原理.

  • category编译过后的底层结构是category_t,里面包含实例方法列表,类方法列表,协议列表,属性列表.通过runtime的方式,合并到类信息(类对象,元类对象)中.

Load 方法

查看源码

**********************************************************************/
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方法.接下来看call_class_loads的实现

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方法的地址直接调用(*load_method)(cls, SEL_load);并不经过objc_msgsend;

如果是继承关系,load方法的调用顺序如何?

 if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // Ensure superclass-first ordering
    schedule_class_load(cls->superclass);

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

总结:

  • 每个类,分类的load方法在程序运行过程中只调用一次.
  • 先调用类的load方法,然后根据编译顺序调用分类的load方法(先编译先调用)
  • 调用子类的load方法前会调用父类的load方法.

initialize

  • 类第一次接收到消息的时候调用,走的是objc_msgsend
  • 会先调用父类的initialize,再调用子类的initialize.
  • 如果分类实现该方法,会优先调用.

objc_msgSend

  
void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    


/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
  • class_initialize. Send the '+initialize' message on demand to any
  • uninitialized class. Force initialization of superclasses first.

Load 和 initialize 区别?

  • load是直接找到该方法地址调用的,而initialize是通过objc_msgSend,在第一次接受消息时候调用.
  • 父类的initialize可能会被调用多次.
  • 分类的initialize被调用的级别高.

补充:关联对象

分类添加属性
在分类中添加


@interface Person (test)

@property (nonatomic,assign) int age;

@end

只会生成set方法.
分类里面不能直接添加成员变量.但是可以通过runtime间接实现.

const void *nameKey = &nameKey;//存自己的地址

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{
    return objc_getAssociatedObject(self, nameKey);
}

other

//static const void *nameKey = &nameKey;//存自己的地址 static 防止外部extern
static const char nameKey2;

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, &nameKey2, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{
    return objc_getAssociatedObject(self, &nameKey2);
}

other

//@"name" 常量区 地址不变
- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{
    return objc_getAssociatedObject(self, @"name");
}

other

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name{
    //_cmd = @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

通过这种方式是不会影响原先类的成员变量的,也就是说不会以runtime的方式添加到类对象的成员变量列表里面.关联的这些属性是单独存放的.具体的可以看下图:


image.png

相关文章

  • Cateogry

    本文主要讲解Cateogry的原理主要手段还是通过查看clang编译后的代码来分析 首先我们直接先创建个ZQmod...

  • Cateogry

    底层结构 新建一个类Person,分类Person+test,用之前学过的命令 当程序编译的时候.会生成这样一个_...

  • Cateogry底层学习

    一、概述 Category,也就是分类,能够在不修改原类代码的基础上,为类增加一些方法和属性。 Category的...

  • iOS Cateogry的深入理解&&load方法调用&&分类重

    首先先看几个面试问题 Cateogry里面有load方法么? load方法什么时候调用?load方法有继承么? 1...

  • 详解iOS中分类Cateogry

    首发于我的个人博客 分类的基本使用 首先我们定义一个类 YZPerson 继承自 NSObject 然后定义一个分...

  • iOS底层解析-----Cateogry解析 关联对象原理

    先抛出 分类Category 的底层结构 Category的用法经常用到,第一印象就是给一个类扩展方法本文主要分析...

  • iOS Cateogry的深入理解&&initialize方法调

    上一篇文章我们讲到了load方法,今天我们来看看initialize 新建项目,新建类(和上一篇文章所建的类相同,...

网友评论

      本文标题:Cateogry

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