一、分类(类别):
背景:
在大型项目,企业级开发中多人同时维护同一个类,此时程序员A因为某项需求只想给当前类currentClass添加一个方法newMethod,那该怎么办呢?
最简单粗暴的方式是把newMethod添加到currentClass中,然后直接实现该方法就OK了。
但考虑到OC是单继承的,子类可以拥有父类的方法和属性。
如果把newMethod写到currentClass中,那么currentClass的子类也会拥有newMethod。但真正的需求是只需要currentClass拥有newMethod,而currentClass的子类不会拥有。
苹果为了解决这个问题,就引入了分类(Category)的概念。
分类运用场景举例:
举例:想要收集每个页面的启动时间。
问题1:
项目中已经有上百个页面了,如果一个一个的加,浪费时间不说,以后增加了新页面,还需要添加方法。
解决方法:
我们可以发现页面都继承了UIViewController,想要在每个页面都执行的代码,可以写在这些页面的父类中。我们可以把代码写在UIViewController中;出现问题2。
问题2:
UIViewController是官方类,我们只能调用期接口,并不能修改他的实现。
解决方法:使用分类(category)。
问题3:
本类是系统的类,这里是UIViewController,我们可以使用分类扩展他的方法,也可以重写他的方法,可是我需要在调用的地方加头文件,所有子类都写头文件和直接在子类写方法没有什么区别,怎么样可以使得不写头文件,子类就能调用我们写的代码呢?
回答:
我们可以进行方法交换(这样可以不必在调用的地方增加头文件),从而使得在实现的时候调用重写的方法。
概念
分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量。
作用
1.可以在不修改原来类的基础上,为一个类扩展方法。
2最主要的用法:给系统自带的类扩展方法。
注意:
1.分类中只能添加“方法”,不能增加成员变量,因为分类的结构体指针中,没有属性列表,只有方法列表;
如果分类中声明了一个属性,那么分类只会生成这个属性的set、get方法声明,也就是不会有实现(调用set方法给该属性赋值会崩溃,调用get方法获取属性值也会崩溃);
原则上讲它只能添加方法,不能添加属性即成员变量,实际上可以通过其他方式(通过getter,setter手段即运行时进行添加)添加属性;
2.分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
3.分类中可以访问原来类中的成员变量,但是只能访问@protect和@public形式的变量。如果想要访问本类中的私有变量,分类和子类一样,只能通过方法来访问;
4.如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类;
5.如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
1.分类不能添加属性的实质原因:
我们知道在一个类中用@property声明属性,编译器会自动帮我们生成_成员变量和setter/getter,但分类的指针结构体中,根本没有属性列表。所以在分类中用@property声明属性,既无法生成_成员变量也无法生成setter/getter。 因此结论是:我们可以用@property声明属性,编译和运行都会通过,只要不使用程序也不会崩溃。但如果调用了_成员变量和setter/getter方法,报错就在所难免了。
如果调用了,报错如下:
//普通声明,不会自动生成setter/getter,
// programmer.nameWithoutSetterGetter = @"无setter/getter"; //调用setter,编译成功,运行报错为:(-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7f9de358fd70')
// NSLog(@"%@",programmer.nameWithoutSetterGetter); //调用getter,编译成功,运行报错为-[Programmer setNameWithSetterGetter:]: unrecognized selector sent to instance 0x7fe22be11ea0'
// NSLog(@"%@",_nameWithoutSetterGetter); //这是调用_成员变量,错误提示为:(Use of undeclared identifier'_nameWithoutSetterGetter')
2.报错的根本原因是使用了系统没有生成的setter/getter方法,我们可以手动添加setter/getter来避免崩溃,完成调用;
由于OC是动态语言,方法真正的实现是通过runtime完成的,虽然系统不给我们生成setter/getter,但我们可以通过runtime手动添加setter/getter方法。
通过运行时手动添加:
#import
static NSString *nameWithSetterGetterKey = @"nameWithSetterGetterKey";//定义一个key值
@implementation Programmer (Category)
//运行时实现setter方法
- (void)setNameWithSetterGetter:(NSString*)nameWithSetterGetter
{
objc_setAssociatedObject(self, &nameWithSetterGetterKey, nameWithSetterGetter, OBJC_ASSOCIATION_COPY);
}
//运行时实现getter方法
- (NSString*)nameWithSetterGetter
{
returnobjc_getAssociatedObject(self, &nameWithSetterGetterKey);
}
@end
运行:
//通过runtime实现了setter/getter
programmer.nameWithSetterGetter =@"有setter/getter";//调用setter,成功NSLog(@"%@",programmer.nameWithSetterGetter);//调用getter,成功 NSLog(@"%@",_nameWithSetterGetter); //这是调用_成员变量,错误提示为:(Use of undeclared identifier '_nameWithSetterGetter')
但是注意,以上代码仅仅是手动实现了setter/getter方法,但调用_成员变量依然报错。
二、类扩展:
Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。
作用:
能为某个类附加额外的属性,成员变量,方法声明;
一般的类扩展写到.m文件中;
一般的私有属性写到类扩展;
三、分类和类扩展的区别:
1.分类格式:
@interface待扩展的类(分类的名称)
@end
@implementation待扩展的名称(分类的名称)
@end
类扩展格式:
@interfaceXXX()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end
2.分类原则上只能添加“方法”,不能增加成员变量,但是可以通过runtime解决无setter/getter的问题,进而添加属性;类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的;
3.类扩展中添加的新方法,一定要实现,没被实现,编译器会报警;categorygory中没有这种限制,类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
4.通常来讲,分类定义在.h文件中,但也可以定义.m文件中,此时分类的方法就变成私有方法;
类扩展可以定义在.m文件中,这种扩展方式中定义的变量都是私有的,也可以定义在.h文件中,这样定义的代码就是共有的,类扩展在.m文件中声明私有方法是非常好的方式。
5.类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
四、关于继承和分类的使用:
以下情况,使用继承:
1)新扩展的方法与原方法同名,但是还需要使用父类的实现。
2)扩展类的属性。
以下情况,使用类别:
1)针对系统特定类,例如:NSString,NSArray,NSNumber等。
2)针对自定义类,对于大型而复杂的类,为提高可维护性,把相关的方法分组到多个单独的文件中。