一、分类
分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量。
不清楚怎么创建分类的先看下怎么创建分类,很简单。
作用:
- 作用:可以在不修改原来类的基础上,为一个类扩展方法。
- 最主要的用法:给系统自带的类扩展方法。
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列表是元类方法列表的一个子集。
但这个结构体里面根本没有属性列表。
注意:
- 分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> 。
- 分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告)。
- 可以在分类中访问原有类中.h中的属性。
- 如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类。
- 如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
下面来看下实际代码:
#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的用户有很多。
类扩展(Class Extension)
Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。
其实开发当中,我们几乎天天在使用。对于有些人来说像是最熟悉的陌生人。
作用:
- 能为某个类附加额外的属性,成员变量,方法声明
- 一般的类扩展写到.m文件中
- 一般的私有属性写到类扩展
**个人理解:**类拓展,当我们创建一个控制器的时候,你会在.m文件里面看到
@interface ViewController ()
@end
这个其实就是一个类拓展,可你可以在里面写方法和属性。但是你创建一个UIView的时候,你会发现它的.m里面并没有类拓展,我们通常都自己会在.m里写一个类拓展,来写是有变量和私有方法,所以类拓展无处不在,我们一直都在用它!这是我个人对类拓展的理解,有不同想法的可以评论告诉我,大家一起讨论。
类别与类扩展的区别:
- 类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已)
- 类扩展不仅可以增加方法,还可以增加实例变量(或者属性)
- 类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中
- 类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现
- 定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式
这里说一下我们常用的刷新第三方库MJRefresh,里面就有用到这种方法,就在Demo里面可以研究下!