iOS-分类(Category)和类扩展(Extension)

一、分类

分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量。

不清楚怎么创建分类的先看下怎么创建分类,很简单。

作用:

  1. 作用:可以在不修改原来类的基础上,为一个类扩展方法。
  2. 最主要的用法:给系统自带的类扩展方法。

Category源码:

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. 分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> 。
  2. 分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告)。
  3. 可以在分类中访问原有类中.h中的属性。
  4. 如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类。
  5. 如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。

下面来看下实际代码:

#import "Person.h"

@interface Person (Category)

@property (nonatomic, copy) NSString *nickname;

@end

**问题1.**你会发现在分类中声明属性,编译并没有报错!既然分类不让添加属性,那为什么我声明属性仍然还以编译通过呢?
**答:**我们知道在一个类中用声明属性的时候,编译器会自动帮我们给成员变量生成相应的setter/getter方法,但分类的指针结构体中,根本没有属性列表。所以在分类中声明属性,就无法生成setter/getter。所以当我们在分类中声明属性时候,编译和运行都会通过,只要不使用这个属性就没事。但如果调用了成员变量的setter/getter方法,程序就会crash

这里写图片描述

这里写图片描述

接下来我们就解决没有setter/getter方法的问题,我们能不能手动给他生成方法呢?答案是肯定的,方法真正的实现是通过runtime完成的,我们可以通过runtime手动添加setter/getter方法。

利用runtime添加setter/getter方法

// 注意这里需要首先要导入#import <objc/runtime.h>
#import <objc/runtime.h>

static NSString *nicknameKey = @"nicknameKey";

// setter method
- (void)setNickname:(NSString *)nickname
{
    /**
     * Sets an associated value for a given object using a given key and association policy.
     * 使用给定的键和关联策略设置给定对象的关联值。
     *
     * @param object The source object for the association.
     * @param object 关联的源对象。
     *
     * @param key The key for the association.
     * @param key 关联的密钥。
     *
     * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
     * @param value要与对象的键关联的值。通过NIL来清除现有的关联。
     *
     * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
     * @param policy关联的策略。有关可能的值,请参见“关联对象行为”
     */
    //方法一:
    //objc_setAssociatedObject(self, @selector(nickname), nickname, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    //方法二:
    objc_setAssociatedObject(self, &nicknameKey, nickname, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
// getter method
- (NSString *)nickname
{
    /**
     * Returns the value associated with a given object for a given key.
     * 返回与给定键的给定对象关联的值。
     *
     * @param object The source object for the association.
     * @param object 关联的源对象。
     *
     * @param key The key for the association.
     * @param key 关联的密钥。
     *
     * @return The value associated with the key \e key for \e object.
     * @return 与对象的密钥相关联的值。
     */
     
    //方法一:
    //return objc_getAssociatedObject(self, @selector(nickname));
    
    //方法二:
    //_cmd在Objective-C的方法中表示当前方法的selector,正如同self表示当前方法调用的对象实例。 _cmd 的作用域只在当前方法里,直指当前方法名 @selector
    //return objc_getAssociatedObject(self, _cmd);
    
    //方法三:
    return objc_getAssociatedObject(self, &nicknameKey);
}
// 删除给定对象某个属性关联,通过value的值置为nil来清除现有的关联
- (void)removeAssociated
{
    objc_setAssociatedObject(self, &nicknameKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
// 删除给定对象的所有关联
- (void)removeAllAssociated
{
    /**
     * Removes all associations for a given object.
     * 删除给定对象的所有关联。
     *
     * @param object An object that maintains associated objects.
     * @param object维护关联对象的对象。
     *
     * @note The main purpose of this function is to make it easy to return an object
     * @note 此函数的主要目的是方便返回对象
     *
     */
    objc_removeAssociatedObjects(self);
}

这样就可很好的使用分类属性了!!怎么样,runtime是不是很强大,runtime的用户有很多,了解更多有关runtime用法。

类扩展(Class Extension)

Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。
其实开发当中,我们几乎天天在使用。对于有些人来说像是最熟悉的陌生人。

作用:

  1. 能为某个类附加额外的属性,成员变量,方法声明
  2. 一般的类扩展写到.m文件中
  3. 一般的私有属性写到类扩展

**个人理解:**类拓展,当我们创建一个控制器的时候,你会在.m文件里面看到

@interface ViewController ()

@end

这个其实就是一个类拓展,可你可以在里面写方法和属性。但是你创建一个UIView的时候,你会发现它的.m里面并没有类拓展,我们通常都自己会在.m里写一个类拓展,来写是有变量和私有方法,所以类拓展无处不在,我们一直都在用它!这是我个人对类拓展的理解,有不同想法的可以评论告诉我,大家一起讨论。

类别与类扩展的区别:

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

这里说一下我们常用的刷新第三方库MJRefresh,里面就有用到这种方法,就在Demo里面可以研究下!

Demo下载地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值