Category
- 概述:Category是OC2.0之后添加的语言特性,Category又叫类别,分类等,能够在不改变原来类的内容基础上,为类增加一些方法(当然还有大家都知道的,虽然不能以正常方式为类添加属性,但是我们可以通过runtime来动态绑定添加,后面会讲到),除此之外Category还有以下功能
- 将类的实现分开写在几个分类里面,这样做的好处是
- 可以减少单个文件的体积
- 可以把不用的功能组织在不同的Category里
- 可以由多个开发者共同完成一个类
- 可以按需加载想要的Category
- 声明私有的方法
- 模拟多继承
- 使用
既然我们说Category只能为类增加方法,而不能添加属性,那么我们就去创建一个分类文件去验证以下,看看具体是个什么样的情况。我们创建了一个Student和他的分类Student+Category
@interface Student : NSObject
@property (nonatomic,copy) NSString *age;
@property (nonatomic,copy) NSString *name;
@end
//Student+Category.h
@interface Student (Category)
@property (nonatomic,copy) NSString *schoolName;
@end
我们运行一下,可以发现他编译成功了并且运行成功了,
放开断点发现崩溃了,看错误日志我们知道了没有setSchoolName方法。那么问题来了,既然我们可以在分类中添加属性,但在调用的时候却会奔溃,这是为什么,我们先来看看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; // 分类所实现的协议列表
}
我们看到了什么,我们有发现实例方法列表,类方法类表,协议列表,但是没有属性列表。我们知道在一个类中用 @property 声明属性,编译器会自动帮我们生成 _成员变量名 和setter,getter方法,但是在分类的指针结构体中,根本就要没有属性类表,所以也就不能生成 _成员变量名 和setter,getter方法,因此我们的程序可以编译和运行都成功了,但是一旦有调用这个属性就会奔溃。
解决方案
- 由于OC是动态语言,方法真正的实现是通过runtime完成的,虽然系统不给我们生成setter/getter,但我们可以通过runtime手动添加setter/getter方法
#import "Student+Category.h"
#import <objc/runtime.h>
static const char *schoolNameKey = "schoolNameKey";
@implementation Student (Category)
- (void)setSchoolName:(NSString *)schoolName{
objc_setAssociatedObject(self, &schoolNameKey, schoolName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)schoolName{
return objc_getAssociatedObject(self, &schoolNameKey);
}
@end
- 总结
- 分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> ;
- 分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
- 可以在分类中访问原有类中.h中的属性;
- 如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类;
- 如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
Extension
- Extension 是Category的一个特例,类扩展与分类相比只少了分类的名称以及.m文件,他常用的形式不是创建一个单独的文件,而是在实现文件中添加私有的成员变量、属性和方法。比如我们平时在创建一个类的时候我们会在.m文件中添加私有的成员变量、属性和方法,当然我们也可以在.h文件中添加,所以说Extension在我们平时的开发中是最常用的
@interface Student ()
{
NSString *_core;
}
@property (nonatomic,copy) NSString *teachName;
- (void)study;
@end
- 总结
- Extension的作用是为一个类添加额外的私有成员变量,属性以及方法
- 一般的类扩展写到.m文件里
Category和Extension的区别
- Category原则中只能增加方法(能添加属性的原因只是通过runtime解决没有setter和getter方法)
- Extension不仅可以增加方法,还可以增加属性,只是属性默认的访问权限是 private
- Extension中声明的方法没有在.m文件中实现,编译器会报警,但是在Category中的方法没有实现的话编译器是不会有任何警告的。这是因为Extension是在编译阶段添加到类中,而Categor是在运行时添加到类中的
- Extension不能像类别那样拥有独立的实现部分,他所声明的方法只能在其对应的类中实现
- Extension定义在.m文件的方法是私有的,定义在.h文件的方法是公有的
- Extension一般用来隐藏类的私有消息,你必须有一个类的源码才能添加一个类的Extension,所以对于系统一些类,如NSString,就无法添加类扩展,如果只是简单的创建Extension文件是能够创建成功的,但是如果你在文件中添加属性或者方法,在程序中一旦使用了该属性或者方法程序就会崩溃,会报找不到相对应的方法错误信息。