美文网首页
Category原理

Category原理

作者: switer_iOS | 来源:发表于2021-07-05 09:38 被阅读0次

    Category 是OC最常用的技术,可以较少侵入和不知道原类内部实现的情况下为原类添加方法 ,属性, 协议。正是因为这个原因,往往优于继承被采用。若不深究原理很有可能对原类造成毁灭的影响,尤其是在+load+ initialize 方法的使用中。所以一直是面试的热点。
    另外一个原因Category也是runtime很重要的技术体现:编译期加载Category数据,运行时写入原始类的数据中。

    面试

    (文末回答,也请评论你遇到的面试问题,共同进步。)

    • Category的基本原理?
    • Category 可以添加+load方法吗?执行顺序?
    • Category 和Extension的区别 ?
    • Category 为什么不能添加成员变量?Category 添加属性要注意什么?

    原理

    每一个分类只是作为原类的扩展,并不会形成一个新的类,不会创建新的类对象。在编译期会把Category中的方法、属性、协议 打包成一个category_t的结构体,在运行时会把每个category_t中的方法、属性、协议添加到原类的class_rw_t中。

    如果添加的方法与原类的方法同名,并不会覆盖掉原始类的同名方法。而是把所有Category的方法、属性、协议数据,合并到一个大数组中
    ,后面参与编译的Category数据,会在数组的前面,然后将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面。最后方法的执行按照OC的消息分发机制执行:

    实例对象根据isa指针找到类对象,在类对象的方法缓存列表里根据方法名查找(缓存列表是一个hash表,根据方法名hash算法算出偏移量找到对应的方法实现),如果缓存中没有找到再去方法列表中查找(如果排序按照二分法查找如果没有排序遍历查找),找到之后根据方法名和苹果的hash算法算出偏移量插入到缓存列表里去。如果没有找到根据类对象的superClass指针去父类的类对象缓存列表和方法列表中查找。如果找到了缓存到子类的缓存列表里,如果继续找一直到基类还是找不到就会动态方法解析,若仍不处理然后就到了消息转发,如果没有实现转发就会跑出错误。

    查找方法时会遍历如下的二维数组:

    [
     [method_t, method_t], //Category A
     [method_t, method_t], //Category B
     [method_t, method_t]  //原始类
    ]
    
    

    遍历数组的第一个元素Category A添加的方法列表时,若找到就会执行,所以这也是分类的方法先调用的原因。

    image

    category_t这个结构体中有实例方法列表,对象方法列表,协议列表,属性列表,唯独没有成员变量列表,这也是为什么在category中添加成员变量的时候会编译都不能通过。

    image

    比较特殊的+load方法,分类的+load方法同样会像实例方法一样,运行时被插入到metaClass的方法列表里去。但是不同的是他不会被分类覆盖,每个分类和原类的+load方法都会在runtime加载类和分类时调用,而且重点是+load的执行并不是通过message_Send 去执行,而是通过load_method这个结构体里的IMP直接执行。

    struct load_method {
    Class class;
    IMP loadMethod
    }
    
    

    但是如果是通过 [Class load] 手动调用的方式就会按照OC消息分发机制执行了,如果子类没有重写load方法会根据superClass去父类中查找并且先执行分类的+load方法。

    用途:

    • 为现有的类添加方法、属性、协议, 往往用于系统的控件和类。
    • hook 类的某一个方法。
    • 按功能或者业务拆分类的实现,比如UIApplication。

    扩展:

    OC 的方法调用机制
    OC 中Class的结构
    OC 中Class的内存管理(关联对象如何释放)

    面试答案

    Category的基本原理?

    在编译期会把Category中的方法、属性、协议 打包成一个category_t的结构体,在运行时会把每个category_t中的方法、属性、协议添加到原类的class_rw_t中。

    Category 可以添加+load方法吗?执行顺序?

    当然可以添加,但是不会覆盖原类的+load方法。

    1.先调用类的+load
    按照编译先后顺序调用(先编译,先调用)
    调用子类的+load之前会先调用父类的+load

    2.再调用分类的+load
    按照编译先后顺序调用(先编译,先调用)和继承没有关系。

    Category 和 Extension的区别?

    Category是运行时才把数据添加到类的结构中,而Extension中的方法原类中没有区别,编译器就加载到了类的结构中。

    Category 为是否能添加成员变量?Category 添加属性要注意什么?

    不可以添加成员变量,因为成员变量存储在class_ro_t这张表里,这张表是只读的不能在运行时修改,而且category_t这个结构体中没有储存成员变量的list。但是可以根据关联对象实现类似的效果。
    可以添加属性到类的属性列表中, 但是没有实现set 和 get方法,只是声名。同样可以通过运行时关联对象,动态为类实现set和get方法。

    objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    objc_getAssociatedObject(obj, @selector(getter))
    
    

    @selector(getter)方法地址作为key把对应的属性value存储到一个全局的AssociatedManager中。

    image

    如有错误或者新的见解欢迎在评论区约谈...

    相关文章

      网友评论

          本文标题:Category原理

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