美文网首页
Category总结

Category总结

作者: SpringAlways | 来源:发表于2020-09-11 11:41 被阅读0次

问题:Object-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方法用继承好还是分类好?为什么?
答案:OC不可以多重继承,但可以用protocol的形式实现多个接口。Category是分类,是面向对象的一种设计思想,可用来拆分逻辑,相对于继承来说,它更灵活、轻量级,但也难于管理。太多的分类会造成逻辑太过分散,会有同名函数互相覆盖的风险。重写一个类的方法用继承还是分类,得具体问题具体分析。关键流程节点、生命周期方法用继承好,工具类、临时的方法用分类好。
分类的源码定义:

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

可见category中有实例方法列表、类方法列表、属性列表、还有类属性列表(没见过),甚至可以有协议列表,就是没有成员变量,说明是不能给分类加成员变量的。这点也是与extension不同的一点,还有就是extension是在编译期就把代码与主类合并在一起的,而category中的方法是在运行时才“合并”的。

看下编译期

clang一个片段分析

static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyClass",
0, // &OBJC_CLASS_$_MyClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition,
};
static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) {
_OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition,
};

可见category的方法和类,都被转化成为static的函数和struct,这就说明了另一个问题:同一编译环境下,分类是不能重名的,同一分类中的方法也是不能重名的。
通过这个片段也能看到另一个点:编译后,分类是会被放在__DATA段,__objc_const.

运行时

分类在运行时的加载,是在runtime库一启动就做了的。

void _objc_init(void)
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

category的加载是在map_images中做的,map_images最后走到objc-runtime-new.mm里面的_read_images,_read_images从方法名就可以看出来是在加载各种镜像文件,什么是镜像文件呢?
1、Executable: 应用的主要二进制(比如.o文件)
2、Dylib:dynamic library,又称 DSO 或 DLL)
3、Bundle: 资源文件,不能被链接的 Dylib,只能在运行时使用 dlopen() 加载

category明显属于executeble中的.o文件。
找到相应代码

/ Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist =
            _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            class_t *cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = NULL;
                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 (isRealized(cls)) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s",
                                 getName(cls), cat->name,
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols 
                /* ||  cat->classProperties */)
            {
                addUnattachedCategoryForClass(cat, cls->isa, hi);
                if (isRealized(cls->isa)) {
                    remethodizeClass(cls->isa);
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)",
                                 getName(cls), cat->name);
                }
            }
        }
    }

这段代码的主干逻辑就是

  • 把category的实例方法、协议以及属性添加到类上
  • 把category的类方法和协议添加到类的metaclass上

通过阅读后续方法栈,可以自己去追,就不贴代码了。

addUnattachedCategoryForClass
remethodizeClass
attachCategoryMethods
attachMethodLists

可以得知:

  • category的方法没有替换原来类已经有的方法,也就是说如果category和原类都有methodA,attach之后,类的方法列表里会有两个methodA

  • category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就返回了,其实后面可能还有一样名字的方法。

方法的列表决定于类的列表。类的加载列表是由文件顺序装载的。也就是我们最说的build phases -> compile sources中的文件顺序。所以,如果同一项目中,同一个类存在同名分类函数,是可能会出现意料不到效果的,你可能调用了别人的函数。所以,起名字前要注意命名空间。

关联对象

最后说下关联对象

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,
                             "name",
                             name,
                             OBJC_ASSOCIATION_COPY);
}

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

关联对象有两个个点:1、如何存储 2、怎么销毁
1、存储于全局AssociationsManager中的一个静态的AssociationsHashMap中,结构为(示意图):

{
    'key' : 对象地址
    'value': {
                        'name1' : 'value1',
                        'name2' : 'value2',
                        ...
                    }
}

也就是说每一个对象的关联属性都是独立存在的,颗粒度是object,不是class.
2、销毁
在objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。

void *objc_destructInstance(id obj) 
{
    if (obj) {
        Class isa_gen = _object_getClass(obj);
        class_t *isa = newcls(isa_gen);

        // Read all of the flags at once for performance.
        bool cxx = hasCxxStructors(isa);
        bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        
        if (!UseGC) objc_clear_deallocating(obj);
    }

    return obj;
}

相关文章

网友评论

      本文标题:Category总结

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