Categories VS Extensions (分类 vs 扩展)
一、Categories(分类)
Categories是一个把单个类定义分作好几个文件的方式。它的目标是通过模块化减少代码量比较大的压力。这避免了你的代码成为1000+行的文件,这样操作和分配精确几乎是不可能的。一个好的类定义是一个独特开发者必须的。
这个图是用多个文件来实现Car类。
在这个模块,我们在不接触它的类原始文件的情况下,使用分类来扩展已经存在的类。我们将学到如何使用这个功能来仿生保护方法(Protected methods)。Extensions和category关联很大,一会再说。
设置
再我们开始试验categories时,我们需要一个类去操作,创建/改变你的已经存在的类,代码如下:
Car.h
#import <Foundation/Foundation.h> @interface Car : NSObject @property(copy,nonatomic)NSString *model; @property(readonly) double odometer; -(void)startEngine; -(void)drive; -(void)turnLeft; -(void)turnRight; @end
它的实现知识一些描述消息,因此我么可以再不同方法调用时发现。
#import "Car.h"
@implementation Car -(void)startEngine { NSLog(@"starting the %@'s engine" ,_model); } -(void)drive { NSLog(@"The %@ is now driving",_model); } -(void)turnLeft { NSLog(@"The %@ is turning left",_model); } -(void)turnRight { NSLog(@"The %@ is turn right",_model); } @end
现在,让我们添其他方法到Car中,我们不去操作Car.h或者Car.m文件,我们可以用一个专用的分类去放置自己的函数方法。
创建Categories
File—>New File—>iOS中选择Source—>Objective-C File,然后就会看到下图的选项,File Type 包括Empty File,Category,Protocol和Extension。
File是文件名,Class是你想要扩展的类。
当我们创建完成后,会生成两个文件,一个Car+Maintenance.h和一个Car+Maintenance.m
Car+Maintenance.m
#import "Car+Maintenance.h" @implementation Car (Maintenance) @end
分类名字的约束是一个类的不同分类名字不能相同。正宗的文件命名协定是用类名+分类名。
在Car+Maintenance.h
#import "Car.h" @interface Car (Maintenance) @end
我们可以看到,它几乎和其他的类一样,只不过多了一个小括号,里面是分类名称,让我们添加一些新的方法到这里:
#import "Car.h" @interface Car (Maintenance) -(BOOL)needsOilChange; -(void)changeOil; -(void)rotateTires; -(void)jumpBatteryUsingCar:(Car *)anotherCar; @end
在运行的时候,他们就会成为类的一部分,尽管他们没有在一个文件中声明,你可以像访问原始文件中的方法一样去访问分类中的方法。
当然了,你必须去实现这些方法。它的实现和原始文件实现几乎一样,除了类名称后面多了一个括号。
#import "Car+Maintenance.h" @implementation Car (Maintenance) -(BOOL)needsOilChange { return YES; } -(void)changeOil { NSLog(@"Changin oil for the %@",[self model]); } -(void)rotateTires { NSLog(@"Rotation tires for the %@",[self model]); } -(void)jumpBatteryUsingCar:(Car *)anotherCar { NSLog(@"Jumped the %@ with a %@",[self model],[anotherCar model]); } @end
还有重要的一点,分类也可以用于重写在原始类文件中已经存在的方法,(例如startEngine method),但是千万不要这样做。原因是分类是扁平式组织结构,如果你重写已经存在的方法,OC就不知道该调用哪个方法,在这种情况下,子类化是一个更好的选择。
使用Categories
要想使用分类只需要把.h文件导入即可,这样分类中的方法即可使用。
#import "Car+Maintenance.h"
如果你删除了分类,那么你就会找不到定义的方法。
保护方法(Protected)
但是,分类并不是简单的把类定义放到几个文件中,他是一个强大的组织工具。我们可以定义类似保护访问的修改器,方法是在一个专用的分类中,定义一个protected 接口,然后之把它导入父类实现文件中,这样就定义了保护方法,而且使它们保持隐蔽。例如我们创建Car+Protected.h,然后直接导入父类的实现文件中。
#import "Car.h" #import "Car+Protected.h" @implementation Car ... - (void)drive { [self prepareToDrive]; NSLog(@"The %@ is now driving", _model); } ...
就像上边这样。
二、Extensions(扩展)
它和分类很像,都是在原始类文件之外,允许你添加新的方法。但是与分类形成对照。extension’API必须再主实现中实现,它不能在分类中实现。
记住一点,私有方法可以用把分类引入到实现文件中(而不是接口文件)实现私有效果,这些再你有很少的私有方法时可以实现,但是对于一个比较大的类就显得比较笨拙,扩展就是解决这个问题,它允许你声明正式的私有API.
例如,如果你想把engineIsWorking方法正式的加入到Car中,你可以在Car.m包括一个扩展.它仍是一个私有方法,扩展的语法就像一个空的分类。
#import "Car.h" @interface Car () -(BOOL)engineIsWorking; @end
它只有一个.h文件。然后在Car.m中去实现:
#import "Car.h" #import "Car_Car.h" @implementation Car -(void)startEngine { NSLog(@"starting the %@'s engine" ,_model); } -(void)drive { NSLog(@"The %@ is now driving",_model); } -(void)turnLeft { NSLog(@"The %@ is turning left",_model); } -(void)turnRight { NSLog(@"The %@ is turn right",_model); } -(BOOL)engineIsWorking { return YES; } @end
另外,为了声明一个正式的私有API,扩展还可以重定义属性。这通常是用来使属性就像读写属性一样,同时保持只读到其他对象中。
例如:
#import "Car.h" @interface Car () @property(readwrite) double odometer; -(BOOL)engineIsWorking; @end
我们可以在内部实现中指定self.odometer的值,但是再Car.m外就会编译失败。
三、总结
分类是把类模块化道不同文件中的一种方式。扩展提供相似的功能,除了他的API必须要在主实现中公开。
在组织大量代码库时,最常见的分类使用就是给内置数据类型(NSString 、NSArray)添加方法。这样的优点在于,你不需要去创建一个子类更新存在的代码,但是必须要小心不要去重写已经存在的方法。对于小的工程,分类完全没有必要使用,可以用协议或者子类化去实现这种功能。