我眼中的 OC【category】 用法细节以及注意点

最近空闲的时候在看OC的面向对象特性,作为Apple的当家开发语言(请暂且忽略swift小兄弟,呵呵),OC近几年可谓风光无限。不过说真的,OC的确有他的美妙之处,一旦用到熟练就会发现OC其实非常顺手通俗。对于他的面向对象特性,“消息机制”、“Protocol”、“Catagory”,这三者绝对是三员大将,也是学习OC必须要掌握的技能。本文将从我个人的视角对catagory的理解,做简单的用法讲解。

另外,对于这三者的使用频率一般来说是,“消息机制”>“Protocol”>“Catagory”。消息机制,这是入门必须要学会的;Protacol,协议,很多Apple的官方API以及我们平时工作中,都是利用协议,回调代理的方式实现MVC中“UI与控制”的分离、去耦合。而Catagory,老实说,之前我是很少用到。刚开始,觉得Catagory和继承很像,经常用继承代替Catagory。经过最近一段时间的刻意使用,我总结了Catagory与Protocal的异同,下文将分要点慢慢道来。

【我不写全面详细的教程,没能力也写不好,本文的末尾会将一些看到的好文章、好教程分享出来,都是大牛们的“教程”,想看的直接点进去哦~】

一,什么是Catagory

Catagory是一种手段,用来“在不改变原来的类的代码的基础上,对原来的类的功能(方法)进行扩充”,它有局限性,如不能扩充变量,只能扩充方法。但它也有他的优势,例如多人合作时,按功能分为不同文件开发。

二,如何创建一个Catagory (demo很简单,如果需要可在这里下载到https://github.com/pigpigdaddy/CatagoryDemo

首先我创建了一个继承自NSObject类的“BaseObject”类,用来模拟一般我们自定义的一个类。然后创建了一个“BaseObject”的catagory,取名叫LogOne。创建好后可以看到,新的文件被自动地命名为BaseObject+LogOne.h和BaseObject+LogOne.m。

(请注意,很多教程中都没有对如何创建一个catagory做说明,我简单说两句,正确的做法是:1,创建新文件,然后选择“Objective-C Catagory”;2,填好你需要的名称,如我的“LogOne”;3,选择你需要catagory的原来的类(就是你需要去扩充功能的类)如我这里的“BaseObject”,有点类似继承时选择父类。)

我在BaseObject里声明并实现了一个方法如下:

1 #import <Foundation/Foundation.h>
2 
3 @interface BaseObject : NSObject
4 
5 - (void)logInfo;
6 
7 @end
 1 #import "BaseObject.h"
 2 
 3 @implementation BaseObject
 4 
 5 - (void)logInfo
 6 {
 7     NSLog(@"BaseObject");
 8 }
 9 
10 @end

接着我又在BaseObject的Catagory,也就是我们刚刚创建出来的“BaseObject+LogOne”声明并实现了如下方法:

1 - (void)logOneInfo;
1 - (void)logOneInfo
2 {
3     NSLog(@"logOneInfo->BaseObject+LogOne");
4 }

OK,到此,我创建并实现了一个最最基本的Catagory,下面我们来看看它是如何运行的。

三,如何使用一个Catagory

(由于个人习惯问题,把demo写成了个One ViewController的结构,其实本并不需要界面,不过也不影响,我们不需要关注模拟器,只需要看着console的输出就可以了)

这时候,我们的BaseObject类就在没有被改变自己的情况下,扩充了功能,也就是BaseObject+LogOne里的新方法。

我们在ViewController.m中,如下实现:

 1 #import "ViewController.h"
 2 #import "BaseObject+LogOne.h"
 3 
 4 @interface ViewController ()
 5 
 6 @end
 7 
 8 @implementation ViewController
 9 
10 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
11 {
12     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
13     if (self) {
14         // Custom initialization
15     }
16     return self;
17 }
18 
19 - (void)viewDidLoad
20 {
21     [super viewDidLoad];
22     // Do any additional setup after loading the view.
23     
24     BaseObject *object = [[BaseObject alloc] init];
25     [object logInfo];
26     [object logOneInfo];
27 }
28 
29 @end

有一点需要注意:#import部分只需要写出你的catagory(BaseObject+LogOne),这样你也可以访问到原BaseObject的内容,反之如果只import原BaseObject.h则无法调用catagory(BaseObject+LogOne)中扩充的方法。

console输出结果:

1 2014-08-12 23:03:57.931 CatagoryDemo[1090:60b] BaseObject
2 2014-08-12 23:03:57.933 CatagoryDemo[1090:60b] logOneInfo->BaseObject+LogOne

可以看到我们扩充的方法可以正常的使用。

四,用Catagory中的方法覆盖原类的方法

假如我们在catagory中重写BaseObject的方法会怎么样呢?请看:

1 #import "BaseObject.h"
2 
3 @interface BaseObject (LogOne)
4 
5 - (void)logOneInfo;
6 - (void)logInfo;
7 
8 @end
 1 #import "BaseObject+LogOne.h"
 2 
 3 @implementation BaseObject (LogOne)
 4 
 5 - (void)logOneInfo
 6 {
 7     NSLog(@"logOneInfo->BaseObject+LogOne");
 8 }
 9 
10 - (void)logInfo
11 {
12     NSLog(@"logInfo->BaseObject+LogOne");
13 }
14 
15 @end

此时如果编译,会有一个黄色的警告,告诉你,这个方法在“primary class”中也被实现了。不过仍然可以运行,结果是:

1 2014-08-12 23:13:09.820 CatagoryDemo[1139:60b] logInfo->BaseObject+LogOne
2 2014-08-12 23:13:09.822 CatagoryDemo[1139:60b] logOneInfo->BaseObject+LogOne

由此可见,我们在catagory中重写BaseObject的方法,那么原方法会被覆盖。那么,如果你不希望这样做,最好在命名方法的时候,加上独特的前缀,来防止自己扩充的方法破坏原有类的方法。毕竟apple给出了一个警告,说明利用catagory这样覆盖原类方法的做法,并不值得推荐,如果你必须这样,其实可以用继承。

五,两个或多个catagory共存

如果一个程序中,有两个或多个catagory,衍生(请允许我用这个词,因为我也不知道用什么词来修饰这个关系比较好,如果你知道,请帮忙纠正,感谢!)自同一个类(BaseObject),会怎么样呢?

我又创建了一个catagory如下:

1 #import "BaseObject.h"
2 
3 @interface BaseObject (LogTwo)
4 
5 - (void)logTwoInfo;
6 
7 @end
 1 #import "BaseObject+LogTwo.h"
 2 
 3 @implementation BaseObject (LogTwo)
 4 
 5 - (void)logTwoInfo
 6 {
 7     NSLog(@"logTwoInfo->BaseObject+LogTwo");
 8 }
 9 
10 @end

然后在Viewcontroller里面修改如下:

1 #import "ViewController.h"
2 #import "BaseObject+LogOne.h"
3 #import "BaseObject+LogTwo.h"

此时如果不import BaseObject+LogTwo.h,我的object是无法调用logTwoInfo方法的。

 1 - (void)viewDidLoad
 2 {
 3     [super viewDidLoad];
 4     // Do any additional setup after loading the view.
 5     
 6     BaseObject *object = [[BaseObject alloc] init];
 7     [object logInfo];
 8     [object logOneInfo];
 9     [object logTwoInfo];
10 }

输出的结果为:

1 2014-08-12 23:18:05.168 CatagoryDemo[1175:60b] BaseObject
2 2014-08-12 23:18:05.170 CatagoryDemo[1175:60b] logOneInfo->BaseObject+LogOne
3 2014-08-12 23:18:05.170 CatagoryDemo[1175:60b] logTwoInfo->BaseObject+LogTwo

完美输出!这说明,两个(或多个,以此类推)“衍生”自同一个类的catagory,可以友好相处,各自发挥自己的作用,实现自己扩充的功能,你唯一要做的就是#import正确的头文件即可。

六,假如两个“衍生”自同一个类的catagory,拥有相同名称的方法,会如何呢?

再来试试:

我把BaseObject+LogTwo改成了这样:

1 @interface BaseObject (LogTwo)
2 
3 //- (void)logTwoInfo;
4 - (void)logOneInfo;
5 
6 @end
 1 #import "BaseObject+LogTwo.h"
 2 
 3 @implementation BaseObject (LogTwo)
 4 
 5 //- (void)logTwoInfo
 6 //{
 7 //    NSLog(@"logTwoInfo->BaseObject+LogTwo");
 8 //}
 9 
10 - (void)logOneInfo
11 {
12     NSLog(@"logOneInfo->BaseObject+LogTwo");
13 }
14 
15 @end

现在连个catagory各自具有一个与对方相同的函数,试试运行效果:

首先编译器给出了一个警告,说两个catagory拥有相同的方法,但是仍然可以运行

1 2014-08-12 23:28:17.272 CatagoryDemo[1250:60b] BaseObject
2 2014-08-12 23:28:22.737 CatagoryDemo[1250:60b] logOneInfo->BaseObject+LogTwo

打印出的是BaseObject+LogTwo中的同名方法,而且每次都是这样,但是为什么呢?这个真的没研究出来。在我看来这两个catagory中的同名方法理论上应该是平等的,或许编译器在运行时,用某种规则做了优先,读者有懂得往告知感谢!

 

总结:

借鉴了http://www.cnblogs.com/ludashi/p/3893084.html的总结

catagory的功能有三:

1.可以用类目给已有的类扩充方法

2.可以用类目把类的实现按功能模块分为不同的文件

3.可以用来扩展NSObject类的方法,也叫做非正式协议(其实就是扩充NSObject,做一个catagory)。

缺点是不能扩展变量。

另外:这篇文章说的很好(需要FQ):http://just-works.blogspot.tw/2013/11/subclass-category-and-extensions-in.html

“An Objective C category allows you add your own methods to an existing class.
Categories are also called as "informal protocols".
Suppose take an example, since Foundation Framework classes such as NSString, NSArray, NSDate etc… doesn’t have any access to modify, you can add your own methods in to these classes by the help of a category.”
“Note that in a category you can’t add an instance variable, since methods within a category are added to a class at runtime.”
说明了为什么不能加变量----catagory是一种编译时(或运行时?)的手段。
 
解释了catagory和继承的区别。

Sometimes, inheritance just seems like more trouble than it is worth. It is correctly used when you want to add something to an existing class that is a change in the behaviour of that class.

With a Category, you just want the existing object to do a little more. As already given, if you just want to have a string class that handles compression, you don't need to subclass the string class, you just create a category that handles the compression. That way, you don't need to change the type of the string classes that you already use.

The clue is in the restriction that categories only add methods, you can't add variables to a class using categories. If the class needs more properties, then it has to be subclassed.(edit: you can use associative storage, I believe).

Categories are a nice way to add functionality while at the same time conforming to an object oriented principle to prefer composition over inheritance.

Edit January 2012

Things have changed now. With the current LLVM compiler, and the modern, 64-bit runtime, you can add iVars and properties to class extensions (not categories). This lets you keep private iVars out of the public interface. But, if you declare properties for the iVars, they can still be accessed / changed via KVC, because there is still no such thing as a private method in Objective-C.

最后:延展(Extension)是匿名的catagory,用于在.m文件中,将一些变量、方法,隐藏在实现文件而不暴露在外。例如系统模版中的:

1 @interface ViewController ()
2 
3 @end

可参考这里:http://www.cnblogs.com/ludashi/p/3893084.html

posted @ 2014-08-12 23:51  pigpigdaddy  阅读(546)  评论(0编辑  收藏  举报