iOS-分类(Category)

作者: 梦蕊dream | 来源:发表于2018-07-17 11:41 被阅读934次

    Category是Objective-C 2.0之后添加的语言特性,分类、类别其实都是指的Category。Category的主要作用是为已经存在的类添加方法。
    Objective-C 中的 Category 就是对装饰模式的一种具体实现。它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。

    分类优点

    • 声明私有方法
    • 分解体积庞大的类文件
    • 把Framework私有方法公开
    • 模拟多继承(另外可以模拟多继承的还有protocol)

    Category源码:

    Category 是表示一个指向分类的结构体的指针,其定义如下:
    typedef struct objc_category *Category;
    struct objc_category {
      char *category_name                          OBJC2_UNAVAILABLE; // 分类名
      char *class_name                             OBJC2_UNAVAILABLE; // 分类所属的类名
      struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
      struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
      struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
    }
    

    结构体主要包含了分类定义的实例方法与类方法,其中instance_methods 列表是 objc_class 中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。

    实现原理

    • 1.我们不主动引入 Category 的头文件,Category 中的方法都会被添加进主类中。我们可以通过 - performSelector: 等方式对 Category 中的相应方法进行调用:
      a)将 Category 和它的主类(或元类)注册到哈希表中;
      b)如果主类(或元类)已实现,那么重建它的方法列表。

    • 2.在这里分了两种情况进行处理:
      a)Category 中的实例方法和属性被整合到主类中;
      b)类方法则被整合到元类中
      c)对协议的处理比较特殊,Category 中的协议被同时整合到了主类和元类中。

    • 3.最终都是通过调用 staticvoid remethodizeClass(Class cls) 函数来重新整理类的数据的。

    分类特点

    • 1.分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性 ;
    • 2.分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量,会编译通过,但是引用变量会报错;
    • 3.如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法,同名方法调用的优先级为 分类 > 本类 > 父类
    • 4.如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
    • 5.运行时决议
    • 6.同名分类方法生效取决于编译顺序
      1. 名字相同的分类会引起编译报错

    加载调用栈
    images表示镜像

    加载调用栈

    分类添加实例变量-关联对象

    image.png
    • 关联对象由AssociationManager管理并在AssociationHashMap存储。
    • 所有对象的关联内容都存在用一个全局容器中,和宿主类无关。
    关联对象本质
    示例代码
    #import <objc/runtime.h>
    /* ---------------.h---------------*/
    @interface NSString (url)
    @property (nonatomic, retain) NSString *url;
    @end
    
    /* ---------------.m---------------*/
    static char imageURLKey;
    @implementation NSString (url)
     
    - (void)setUrl:(NSString *)url
    {
        objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (NSString *)url
    {
        return objc_getAssociatedObject(self, &imageURLKey);
    }
    @end
    

    objc_getAssociatedObject

    • 这个函数先根据对象地址在 AssociationsHashMap 中查找其对应的 ObjectAssociationMap 对象,如果能找到则进一步根据 key 在 ObjectAssociationMap 对象中查找这个 key 所对应的关联结构 ObjcAssociation ,如果能找到则返回 ObjcAssociation 对象的 value 值,否则返回 nil;
    • objc_getAssociatedObject有两个参数,第一个参数为从该object中获取关联对象,第二个参数为想要获取关联对象的key;
    /** 
     * Returns the value associated with a given object for a given key.
     * 
     * @param object The source object for the association.
     * @param key The key for the association.
     * 
     * @return The value associated with the key \e key for \e object.
     * 
     * @see objc_setAssociatedObject
     */
    OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
    

    objc_setAssociatedObject
    参数说明:

    • object和key同于objc_getAssociatedObject;
    • value:需要和object建立关联引用对象的value;
    • policy:关联策略,等同于给property添加关键字
    /** 
     * Sets an associated value for a given object using a given key and association policy.
     * 
     * @param object The source object for the association.
     * @param key The key for the association.
     * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
     * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
     * 
     * @see objc_setAssociatedObject
     * @see objc_removeAssociatedObjects
     */
    OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
    

    扩展

    • 声明私有属性
    • 声明私有方法
    • 声明私有成员变量
    • 编译时决议
    • 只以声明存在,寄生于宿主类.m中
    • 不能为系统类添加扩展

    分类、扩展区别

    • ①分类中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
    • ②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(
      用范围只能在自身类,而不是子类或其他地方);
    • ③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
    • ④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
    • ⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。

    其他

    类源码:

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
        long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
        long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
        long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
        struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
    #endif
    } OBJC2_UNAVAILABLE;
    

    在上面的objc_class结构体中,ivars是objc_ivar_list(成员变量列表)指针;methodLists是指向objc_method_list指针的指针。在Runtime中,objc_class结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList是一个二维数组,所以可以修改*methodLists的值来增加成员方法,虽没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。

    深入篇:iOS-Category原理

    相关文章

      网友评论

        本文标题:iOS-分类(Category)

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