3. 分类,类扩展
2020-06-24 本文已影响0人
如果大雨落下
分类
作用: 声明私有方法,分解体积大的类文件,把framework的私有方法公开
Category源码:
Category
Category 是表示一个指向分类的结构体的指针,其定义如下:
typedef struct objc_category *Category;
struct objc_category {
const char *name OBJC2_UNAVAILABLE; // 分类名
Struct _class_t *cls; 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; // 分类所实现的协议列表
struct objc_properties_list *properties OBJC2_UNAVAILABLE; // 属性列表
}
- 在编译的时候,会将每一个分类都转换成一个category_t的结构体,里面会存放原类的名称,实例方法列表,类方法列表,协议列表,还有属性列表
- 在程序运行的时候,会将所有分类的方法,属性,协议等东西添加到原类里面
- 添加顺序,是先将原类的存储数组通过memmove往后面移动,然后再将添加的分类信息copy过来,所以分类的信息会放在数组的前面,这也是为什么会优先调用分类方法的原因,如果分类里面有同样的方法,取决于分类的编译顺序,越后面编译的分类方法,越放在前面,越先被调用,这个里面的方法并不是被覆盖,其他分类方法也可以调用,只是执行顺序的问题
3. 分类,类扩展- +load方法适合例外,是系统直接调用,其他方法是通过消息发送机制调用,所以会一级一级的查找,
- Load的调用顺序都是优先原类,再调分类
- Load方法的指针是单独记录的
- 全局load方法的执行顺序,是按照编译顺序,先将每个原类的load方法加入数组,但是碰到了有父类的,先将父类的load加进去,分类的Load是直接按照编译顺序加进去,然后优先调原类的,再调分类的
- 分类可以添加属性,因为分类的结构中有属性列表参数,但是分类的属性,不会自动生成成员变量,即_age这样的,而且只会声明get和set方法,但是不会生成方法的实现,如果要正常使用,就要自己增加方法的实现,通过runtime,用associ的方法来实现,里面的key,需要存一个地址值,只要保证唯一,可以用各种地址值,最优方案是用方法@selector的地址值,用get或set方法,那其中一个就可以用_cmd表示,_cmd就代表当前方法,每个方法有两个隐式参数,self和_cmd
-
分类添加的属性并不会增加到原类当中,而是单独存储在一个地方,associetemanger,里面用hashmap来存储,相当于两层字典,第一层的key用的就是添加属性时传进去的object,也就是self,第二层的key里面存的就是添加时传进去的key,也就是一个唯一的地址值,存的值就是传进去的值,和值相关的policy(retain,copy等)
注意:
- 分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> ,只能通过关联对象(objc_setAssociatedObject)来模拟实现成员变量,但其实质是关联内容,所有对象的关联内容都放在同一个全局容器哈希表中:AssociationsHashMap,由AssociationsManager统一管理。
- 分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
- 可以在分类中访问原有类中.h中的属性;
- 如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类;
- 如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
- 一个类中用@property声明属性,编译器会自动帮我们生成成员变量和setter/getter,但分类的指针结构体中,根本没有属性列表。所以在分类中用@property声明属性,既无法生成成员变量也无法生成setter/getter。
因此结论是:我们可以用@property声明属性,编译和运行都会通过,只要不使用程序也不会崩溃。但如果调用了_成员变量和setter/getter方法,报错就在所难免了。
类扩展(Class Extension)
Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。
就是.m 文件中声名的
@interface XXX ()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end
分类与类扩展的区别:
-
分类中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
-
类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的(用范围只能在自身类,而不是子类或其他地方);
-
类扩展中声明的方法没被实现,编译器会报警,但是分类中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而分类是在运行时添加到类中。
-
类扩展不能像分类那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
-
定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。