在OC中,对于已有的类进行扩展,我们有两种方式:
1、在原始类的定义中,进行代码扩展。
2、通过继承的方式,扩展子类。
3、使用分类的方式。
第一、二种方式不用多说,第三种方式则是OC中比较有特色的功能。
分类允许我们在不更改类的原始代码的情况下,实现对类的功能扩展,包括:添加实例方法,类方法与实例变量,属性(添加实例变量与属性需要匿名分类——类扩展)。
如何定义分类\类扩展定义分类,很简单,只需要指出你需要扩展的类,分类名称和你要定义的分类的方法即可。其余均类似定义普通的类。形式如下
@interface 要扩展的类名称(分类名称) 。。。 @end分类根据有无分类名称,可以分为 分类 和 类扩展(匿名分类)。
而分类又可根据其实现方式,分为两种:
1、分别新建分类.h与分类.m文件,在独立于原始类的文件中编写分类代码。
2、在原始类文件的.h或.m中声明分类,在原始类的.m文件中实现分类。
新建.h与.m,编写类的分类
例如,我们写了一个Car类,定义如下:
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property(nonatomic, strong)NSString* name;
@property(nonatomic, getter=leftGas) NSUInteger totalGas;
-(instancetype)initWithName:(NSString*) name;
-(void) run;
-(void) addGas:(NSUInteger) gas;
@end
这一个基本的Car类,让我们的汽车可以行驶(执行run方法)。但是,某天我突发奇想,想让我的汽车能够飞起来,于是我想在Car中添加fly方法,但又不想改写原始的类文件,OK,这时候就用到了类的分类,分类名称fly,定义如下:
#import "Car.h" // 注意,我们这里导入了Car.h文件,让分类知道Car类的存在
@interface Car (Fly)
-(void) fly;
@end
在分类的实现文件中,有如下代码来实现飞行:
#import "Car+Fly.h"
@implementation Car (Fly)
-(void) fly {
NSLog(@"%@ is flying", self.name); // 这里通过self存取方法,获得Car类的name属性值
}
@end
上面的代码值得注意的一点是,fly函数通过self方式取得了name属性,这里其实表明分类对于类属性的访问,其实是和类本身定义一样的。(但不能够通过_name属性名称直接访问属性,否则会提示未定义)。这一点同继承不同(属性对于子类是不可见的,分类只能通过继承得到的存取方法访问属性)。
关于新建.h与.m编写新的分类,有这么几点注意:
1、分类能够像类本身一样,调用self来访问类的方法,属性。
其实将分类理解成类本身更好,因为除了声明方式不一样外,分类对原始类方法,属性,及实例方法的访问,均与类本身访问方式无二。但对于通过新建.h与.m来写的分类来说,分类对于原始类的了解(即知道其中存在哪些方法,属性),仅通过原始类的接口文件(.h文件),这与在类外部方法类定义的一些内容是一样的,仅仅能够看到.h中的定义。
比如,我们在原始类的.m文件中,定义了一个fire方法,而在.h文件中并没有声明该方法(fire此处为私有方法)
Car.h,没有对fire的声明
@interface Car : NSObject
@property(nonatomic, strong)NSString* name;
@property(nonatomic, getter=leftGas) NSUInteger totalGas;
-(instancetype)initWithName:(NSString*) name;
-(void) run;
-(void) addGas:(NSUInteger) gas;
@end
Car.m 直接实现没有声明过的fire方法,该方法在类外部不可见,但类中可以通过[self fire]形式调用
@implementation Car
-(void) fire { NSLog(@"%@ fire!", self.name);
}
@end
那么对于fire方法,在分类fly中是无法调用到的,若调用该方法,则会提示未定义的错误。
同样的,分类对于原始类的属性的调用,仅能够通过存取方法,若直接用属性名称的话(属性名称仅在原始类的.m中可见),则会提示未定义错误。
2、对于新建.h,.m分类文件,我们的命名应当遵循XCode的默认命名规则。
在Xcode中创建类的分类文件时,会默认以下面方式命名:类名+分类名.h/.m
例如,对于Car的fly分类,我们应该命名文件Car+fly.h Car+fly.m
3、在使用分类的扩展方法时,需要导入分类的.h文件,才能让执行代码知道分类扩展方法的存在。实际上,由于在分类文件中我们已经导入了原始类的头文件,所以在使用分类时,仅仅导入分类头文件即可。
4、分类能够对实例方法,类方法进行扩展,但不能够添加类的属性及实例变量。
在原始类文件的.h或.m中声明分类,在原始类的.m文件中实现分类通过改写原始类的方式实现分类,个人感觉用处不大(这和直接改写原始类有什么区别呢?)。
在原始类中编写分类,,与新建文件编写分类的区别有:
1、对于原始类的可视程度,在原始类中编写的分类是和类本身一样的(即可访问私有方法及通过属性名访问属性),这与新建文件编写的分类是不同的。
类扩展类扩展其实是一种特殊的分类,即匿名分类,如下格式
@interface Car ()
-(void) fire;
@property(nonatomic) NSUInteger price;
@end
括号中并未给出分类名称,这种分类有个专业名称——类扩展。
类扩展与分类的区别如下:
1、类扩展仅能够在原始类中声明(.h或.m中均可,在.m中声明的类扩展其定义的属性和方法均是私有的)
2、类扩展的实现仅能够在原始类的.m中编写。
3、在类扩展中可以扩展类的属性,而在分类中仅能够扩展实例方法和类方法。
对于类分类,有几点比较有意思:
1、对于Cocoa中的类,我们也可以进行分类扩展,特别是对于NSObjec类,我们对其扩展,那么所有的类均可以调用我们的扩展方法!
2、分类扩展可以继承。
3、对于分类或扩展中声明的方法,我们并不要必须实现,而是在必要时,才会有某个类来实现,这点和协议很像。
4、对于类中的同名方法,分类扩展会覆盖其实现。
5、对于类的私有方法,我们可以在分类扩展中将其声明为可见的(而不实现),这样在类外部就可以调用该类的私有函数了。