iOS-分类(Category)
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.同名分类方法生效取决于编译顺序
- 名字相同的分类会引起编译报错
加载调用栈
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指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。