10.Object-C--浅谈Category分类
今天呢,我又要开启我的bibi模式了,首先我给大家出个问题:假如有一个需求是让你扩充类,这时候你会怎么做?
可能我们想到最多的就是使用继承。其实啊!在OC中有一种除了继承之外的另一种方法:分类(Category)。
那什么是分类呢?
简单来说,就是在不改变原先类前提下,我们可以添加咱们自定义的方法,这样和同事合作开发的时候,是不是顺畅的多啦?但是使用分类的时候不能向原先类中增加成员变量,分类方法实现中可以访问原来类中的成员变量。
下面我先举一个简单的例子:
现在有一个Person类,里面有一个study方法,现在想在不改变Person类的基础上添加一个test方法。那么我们就可以新建一个分类如下:
示例代码如下:
1 //Person.h
2 #import <Cocoa/Cocoa.h>
3
4 @interface Person : NSObject
5 - (void)study;
6 @end
7
8 //Person.m
9
10 #import "Person.h"
11
12 @implementation Person
13 - (void)study
14 {
15 NSLog(@"Person 类中的study方法");
16 }
17 @end
18
19 //分类Person+QYMa.h
20 #import "Person.h"
21
22 @interface Person (QYMa)
23 - (void)test;
24 @end
25
26 //Person+QYMa.m
27 #import "Person+QYMa.h"
28
29 @implementation Person (QYMa)
30 - (void)test
31 {
32 NSLog(@"Person+QYMa 中的test方法");
33 }
34 @end
35 //main方法 测试
36 #import <Foundation/Foundation.h>
37 #import "Person.h"
38 #import "Person+QYMa.h"
39
40 int main(int argc, const char * argv[]) {
41 @autoreleasepool {
42
43 Person *p = [[Person alloc]init];
44 [p study];
45 [p test];
46 NSLog(@"Hello, World!");
47 }
48 return 0;
49 }
测试结果:
通过这个示例,咱们来细说一下分类的、声明实现格式:
1 @interface 类名(分类名称) //建议分类名称为模块名称
2 //方法的声明
3 @end
1 @interface 类名(分类名称) //建议分类名称为模块名称
2 //方法的声明
3 @end
接下来咱们来说说,分类一般的使用场景
- 在定义类的某些情况下(例如需求变更),你可能想要为其中的某个或几个类中添加新的方法。
- 一个类中包含了许多不同种类的方法需要实现,而这些方法需要不同团队的成员实现。
- 在使用基础类库的类时,有可能希望这些类实现一些自己需要的方法。
在这里呢,需要说一下如果出现相同方法名的方法的时候,优先在分类中找,然后在原来类中找,最后再父类。分类可以实现原来类中的方法,相当于覆盖了原来的方法,使原来类中的方法失效。所以不建议同名。那么还有一种情况:假如有两个分类同名方法都有实现的话要怎么调用?这个就和文件编译顺序有关,最后编译的会覆盖之前的,所以执行的是最后编译的那个一个。
好接下来,我们举一个扩充NSString类方法的例子:要求实现:计算某个字符串中阿拉伯数字的个数。
我们先新建一个分类:
示例代码如下:
1 //NSString+Number.h 2 #import <Foundation/Foundation.h> 3 4 @interface NSString (Number) 5 + (int)numberCountofString:(NSString *)str; 6 - (int)numberCount; 7 @end 8 9 //NSString+Number.m 10 #import "NSString+Number.h" 11 12 @implementation NSString (Number) 13 + (int)numberCountofString:(NSString *)str 14 {//1.定义变量计算数字的个数 15 // int countNum = 0; 16 // for (int i = 0; i < str.length; i++) 17 // { 18 // unichar c = [str characterAtIndex:i]; 19 // if (c >= '0' && c <= '9') 20 // { 21 // countNum++; 22 // } 23 // } 24 // return countNum; 25 return [str numberCount]; 26 } 27 28 - (int)numberCount 29 { 30 int count = 0; 31 for (int i = 0; i < self.length; i++) 32 { 33 unichar c = [self characterAtIndex:i]; 34 if (c >= '0' && c <= '9') 35 { 36 count++; 37 } 38 } 39 return count; 40 } 41 @end 42 //main 测试代码 43 #import <Foundation/Foundation.h> 44 #import "NSString+Number.h" 45 int main(int argc, const char * argv[]) { 46 @autoreleasepool { 47 48 int num = [NSString numberCountofString:@"1213Qweq1"]; 49 // int num = [@"1213Qweq1" numberCount]; 50 NSLog(@"字符串中阿拉伯数字的个数:%d",num); 51 } 52 return 0; 53 }
测试结果如下:
通过这两个例子,估计分类我们也理解的差不多了。下面咱们来说说继承和分类的区别:
Category是对一个功能完备的类的一种补充,就像是一个东西的主要基本功能都完成了,可以用category为这个类添加不同的组件,使得 这个类能够适应不同情况的需求(但是这些不同需求最核心的需求要一致)。找个就像你已经有了一辆能够开动的汽车一样,我们可以用Category为你的汽 车添加各种之前没有的功能,最后让这辆汽车变成超级跑车一样。当某个类非常大的时候,Category可以按不同的功能将类的实现分在不同的模块中实现。
继承则是都可以完成上面的工作,但是继承有很大的代价问题,一是通过继承来进行扩展是一种耦合很高的行为,对父类可以说是完全依赖;二是继承由于 对父类依赖,所以开发代价相对大,要求对父类的工作流程相对熟悉;三是继承体系如果太复杂会导致整个系统混乱,难以维护。所以在能够分类的时候,就千万不要使用继承。什么情况才是迫不得已要使用继承呢?那就是如果你既想提供一系列接口的定义,同时又想提供一些但是又不能提供全部的实现的 时候,这种情况就要使用继承了。
虽然Category可以访问类的实例变量,去不能创建新的实例变量,如果要创新的实例变量,请使用继承。
在Category中,不提倡对原有方法进行重载。原因非常简单,在Category中进行重载,无法对原方法进行访问,而继承中可以使用super。如果真的需要对原方法进行重载,请考虑继承。
一个类可以定义多个Category,但是如果不同Category中存在相同方法,编译器无法决定使用哪个Category;
在定义Category时,我们可以仅仅给出方法定义,而不需要给出具体的实现。这在程序增量开发时是非常有帮助的;
Category是可以被继承的。在某个父类中定义了Category,那么他所有的子类都具有该Category;
在需要为某个类创建私有成员方法时,也用Category的方式来实现。
Category不能完全代替子类,有以下几个最大的缺点:
当在Category中覆盖一个继承的方法,在Category中的方法可以通过向super类发送一个消息来调用被继承的方法。但是,如果Category中覆盖的那个方法已经在这个类的其它Category定义过了,则之前定义的方法将没有机会被程序调用。
在Category中无法确定其能够可靠的覆盖某个方法,而这个方法已经在其它的Category中定义过。这个问题在使用Cocoa框架时尤其 突出。当你想覆盖某个框架已经定义好的方法时,该方法已经在其它Category中实现,这样就无法确定哪个定义和实现会被最先使用,带来很大的不确定 性。
如果你重新覆盖定义了一些方法,往往会导致这个方法在整个框架中实现发生了变化。
所以鉴于以上原因,在选择继承还是分类的时候,多考虑一些因素。祝大神们都写出更好的代码!
好啦!今天的bibi,就到此结束啦!感慨一下,外面还在下雨。-_-#。还能不能愉快的玩耍了?