iOS中 category 和extension的区别

原文链接:http://www.cnblogs.com/pengyingh/articles/2348514.html

Category和Extension这两个概念,即便对于Java程序员,可能也会感到陌生。这是Objective C为程序员提供的两个强大的动态机制——简单地说,它们允许程序员为已有的对象添加新的方法,即便是在没有该对象的源代码的情况下。

Category准确的定义是这样的:Category拥有一个名字,它不需要使用子类(Subclassing)机制,就允许向一个类文件中添加新的方法声明,并且在类实现的文件中的同一个名字下定义这些方法。其语法举例如下:

 

#import "ClassName.h"
 
@interface ClassName ( CategoryName ) 
// method declarations 
@end

 

不过到现在为止,Category这个名字看起来仍然让人摸不着头脑——Category的中文是分类和范畴的意思——即便这个动态机制很强大,跟分类有什么关系呢?

这是因为利用这个机制,程序员可以把一堆方法分门别类,分成若干组,每组方法用一个Category名字加以命名,定义在同一个文件里。这个就是为什么把这个机制叫做Category的原因。

注意Category只能用于方法,不能用于成员变量。

 

理解了Category,Extension就不难理解了。Extension是Category的一个特例,其名字为匿名(为空),并且新添加的方法一定要予以实现。(Category没有这个限制)

 

下面这里是翻译的Objective-C-primer.pdf 中的相关资料,可以对这两个概念有一个初步了解。


Categories and Extensions

  

Categories

 

catgory 允许你为一个已经存在的类增加方法----甚至是一个你没有source的类。

Categories是一种强大的特性,它允许你直接扩展类的功能,而不需要使用子类的方法来扩展。

使用categories,你可以把你自己的类的实现方法分布在几个不同的文件里。

Class extensions与此相似,但是它允许在implementation代码块额外的增加自己需要的APIs,而不是在原始类的@interface代码块里,  extension中声明的方法是私有的,只有主implement能调用,外部的类无法调用。

 

Adding Methods to Classes

 

可以通过在一个带category name的interface file中声明它们,来为一个类增加方法,你也是可以在带同样category name的implementation file里定义它们。

category name表明这些方法增加到一个在别处被声明的类里,而不是一个新类。

但是是要注意的是,你不能使用category来为一个类增加额外的实例变量。

 

category 增加的这些方法的会成为类类型的一部分。例如,编译器会认为这些通过category的方式增加到NSArray类里面的方法就是NSArray实例中的一部分。

而那些通过继承NSArray的方式,增加到NSArray子类里的方法则不会被包含在NSArray类型里。

 

Category methods可以做任何在类中正常定义的方法能做的事。在运行时,没有任何区别。通过category 增加到类中的方法会被这个类的所有子类继承,就和此类的其它方法一样。

 

Category的声明看赶来和一个interface的声明非常相似(除了category的名字要列在类名后的括号里和没有指明超类之外)。除非它的方法不会访问任何类的实例变量,否则category必须import它扩展的类的文件里来,如下:

 

#import  "ClassName.h "

 

@interface ClassName ( CategoryName )

//method declarations

@end

 

通常在实现文件里要import自己的头文件。一个通常的命名方式是category扩展的类名+

category name。category的实现(在ClassName+CategoryName.m文件里)可能会像下面这样:

 

#import  "ClassName+CategoryName.h"

 

@implementation ClassName ( CategoryName )

//method definitions

@end

 

需要注意的是category不可以为要扩展的类声明额外的实例变量;它只能包含方法。

然而,所有在类的作用域里的实例变量也在category的作用域里。前面的实例变量指的是

类里能声明过的实例变量,@private的也不例外。

 

为一个类增加categories的数量是没有限制的,但是每一个category 的名字必须要是不

相同的,而且应该声明和定义一个不同的方法集。

 

How You Can Use Categories

 

使用categories的方式有很多:

 

  • 扩展一个其它实施者定义的类

   例如,你可以为Cocoa frameworks里的类增加方法。增加的方法会被子类继承

 而且在运行时也不会和原始的方法有任何不同。

 

  • 作为子类的一个替代方式

   不需要定义一个子类来扩展已有的类,通过category你可以直接为类添加方法。

 例如,你可以为NSArray和其它的Cocoa classes添加categories.与添加子类的

 方式来比,你不需要你扩展的类的源代码。

 

  • 把实现一个新类的方法分布在多个源文件里

   例如,你可以把一个很大的类的方法分组到几个categories里,然后把每个     category放在自己的文件里。当以这种方式使用时,    categories在很多方面对开发过程都是有帮助的:

 

   1.提供一个简单的方式来组合相关的方法。被定义在不同的类里的

     相似的方法可以被保存在同一个源文件里。

 

         2.当一个类是由多个开发者共同定义的时候,可以简化大类的管理。

 

   3.为一个非常大的类的增量编译提供方便。

 

   4.提高常用方法的本地参考。

 

   5.可以根据不同版本的程序配置不同的类,而无需为不同版本保持相同的源代码。

 

  • 可以用来声明非正式协议

   例如:@interface NSObject ( MyXMLSupport )

- initFromXMLRepresentation: (NSXMLElement *)XMLElement;

- ( NSXMLElement *)XMLRepresentation;

      @end

 

虽然Objective-C语言目前允许使用category来通过重载继承的类的方法或者甚至是类文件中的方法,但是这种做法是被强烈反对的。category不是子类的替代品。使用category 来重载

方法有很多重大的缺陷:

 

  • 当category 重载一个从父类继承过来的方法,通常可以通过super关键字来调用父类的实现方法。然而,如果category重载一个扩展类本身存在的方法,就没有唤醒原始实现方法的办法了。

 

  • 同一个类的category不能声明重载这个类的另一个category中声明的方法。

   这一点非常的重要,因为很多Cocoa类也是通过使用categories来实现的。

   一个你试图重载的框架中定义的方法可能本身就已经在一个category被实现了,

   如果你这样做了,很可能使用得前面的category的方法的实现失效。

 

  • 一些category methods的存在可能会导致整个框架的行为发生变化。

   例如,如果你在NSObject的一个category中重载windowWillClose:委托方法,

   在你的程序里所有窗口的委托将会使用category方法来回应;所有NSWIndow

   实例的行为都会改变。你为一个框架类增加的Categories可能会导致行为上很神秘

   的变化和程序的崩溃。

 

Categories of the Root Class

 

Category 可以为任何的类添加方法,其中也包括root class。添加到NSObject类上的方法

对于所有与你的代码相关联的类都是可用的。有时候为root class添加方法是非常有用的,

但是它也是非常危险的。虽然从表面上看起来category所做出的修改可以被很好的理解,

而且影响也是有限的,但是继承的机制使得它有了一个广泛的作用域。你可能会对你程序

里不可见的类做出意想不到的修改;你可能会对你正在做的事会产生的结果一无所知。

甚者,当对你修改过什么一无所知的人在你的程序上工作时,他们对于他们正在做的事也

不会有一个充分的了解。

 

另外,当你为root class实现方法时有两点需要记住:

  • 发送消息给super是非法的(因为NSObject没有超类)
  • 类的对象可以执行root class中定义的实例方法

 

正常来说,类对象只能执行类方法。但是root class中定义的实例方法是一个特例。它们

定义了一个类,在运行时系统中的所有对象都继承这个类。类对象是完全成熟的对象,它

需要共享同一个类。

 

这个特性意味着你为NSObject类在category定义的实例方法不仅要能被实例对象执行,

而且也要能被类对象执行。例如:在方法体中,self可能代表一个类对象,也可能是类的

一个实例。

 

Extensions

 

除了它所声明的方法必须要在相应类的主要@implementation代码块被实现以外,

的extensions就像一个匿名的categories。

 

一个类有一个公开声明的API,同时有额外的方法声明为仅由类或框架类私有使用,

这是很正常的。你可以在上面提到的一个私有的头文件或实现文件里用一个category

(或多于一个的category)来声明这样的方法:    

  {     private方法

        Objective-C中的private方法是通过category实现的,在实现文件中我们声明一个类的category,在这里面的方法就是  private方法。类的对象是不可以进行调用的,同样由于该方法的声名是在类的实现文件中,所以子类也是不能重写该方法的。

这样是可行的,但是编译器并不能确认所有被声明的方法都被实现了。 

 http://3426724.blog.51cto.com/3416724/696723  

 }

例如,下面代码里的声明和实现在编译器里并不会报错,甚至setNumber: 方法没有实现

也不会有错:

 

@interface MyObject : NSObject

{

NSNumber * number;

 

- (NSNumber *)number;

@end

 

@interface MyObject ( Setter )

- (void)setNumber : (NSNumber *)newNumber;

@end

 

@implementation MyObject

 

- (NSNumber *)number

{

return number;

}

@end

 

然而,在运行时如果调用setNumber:方法,将会产生错误。

 

Class extensions允许你在本地为一个类声明额外需要的方法,而不需要在原始类的

@interface代码块去添加,正如下面的例子所示:

 

@interface MyObject : NSObject

{

NSNumber * number;

}

 

- (NSNumber * )number;

@end

 

@interface MyObject()

- (void)setNumber: (NSNumber *)newNumber;

@end

 

@implementation MyObject

 

- (NSNumber *)number

{

return number;

}

 

- (void)setNumber:(NSNumber *)newNumber

{

number = newNumber;

}

@end

 

上面的例子中,有几点要注意的:

 

  • 在第二个@interface代码块的括号里并没有给出名字。

 

  • setNumber: 方法的实现出现在类的主@implementation代码块。

 

setNumber: 方法的实现必须得在类的主@implementation代码块里(你不能在category

里实现它)。如果不这样,编译器将会产生一个找不到setNumber:方法定义的警告。

 

看完上面的内容,应该有了一些初步的认识了,下面在具体说下应用方面例子。

 

Class extensions 被设计出来的目的是为了解决二个问题。

第一就是便利编译器能更好的验证类的私有接口,第二个目的就是解决一个微秒而粗糙的properties(另一个objective-c 2.0的特性)问题.


有关更好的验证类的私有接口:

当实现一个类,通常在类的@implementation块会有一个方法集。它们作用于整个@implementation块,在其它所有方法的之前实现,这样当有其它方法用到这些私有方法

时 ,就不会有警告出现(如果它们被实现在最下面,那么编译器会发出警告)。

但这样的实现方式是很粗笨的,我们可以把所有私有方法的声明放在一个category里面,然后把这个category放在.m实现文件顶部。

就像下面这样:

 

  1. @interface MyClass (SuperSecretInternalSauce)  
  2. - (void) doMyPrivateThing;  
  3. - (BOOL) canMyPrivateThingEatThis: (OtherClass *) aThing;  
  4. @end  
  5.   
  6. @implementation MyClass  
  7. ...  
  8. @end  


这些方法将不会在相应的@implementation MycClass (SuperSecretInternalSauce) 块里被实现,当然它们也不是一定要被实现的。

 

但这样做的结果是编译器将不会做确保你实现了所有在category里声明的方法的检查,换句话说,编译器也不会捕获方法声明中的拼写错误。

这是因为category如果没有相应@implementation MycClass (SuperSecretInternalSauce)实现块,那么在objective-c里它就是一个非正式协议

它就是一个方法声明集,里面的方法可以有选择的去实现,通常这种category会被声明在这个类的了类里。

由于 class extension 有效的扩展类的主接口,那么把上面的一段声明代码改成下面这样,也可以达到同样的效果。

 

  1. @interface MyClass ()  
  2. - (void) doMyPrivateThing;  
  3. - (BOOL) canMyPrivateThingEatThis: (OtherClass *) aThing;  
  4. @end  
  5.   
  6. @implementation MyClass  
  7. ...  
  8. @end  


这样修改之后,如果类的@implementation块里没有包含在extension中声明的方法的实现,编译器将会发出抱怨。


有关设计public readonly,private readwrite 的properties:

当我们设计属性时,通常不会设计得太强大。为实现这个目的,可以把它声明在一个categories里,通过特别的synthesis这个属性,可以对它达到功能性的限制

或者完全禁止。

注意:synthesis在categories里是被禁止的,因为synthesis需要存储,而在category里不能有效的声明一个存储区,允许category合成访问类实例变量的方法是不可接受的,

这样太脆弱也太丑陋了。

然而,为了达到内部类和框架的目的,声明一个对公共来说是只读,而对私有来说可以读写的property也是可取的。

一个额外的需求是synthesis 这样的properties必须总是能原生而精确的synthesize setter和getter方法。特别是当声明一个atomic的property,

开发者没有办法正确的手动编写1/2的getter setter方法对;也没法保证锁定的资源不外露,这就是说,在这种情况下没法保证原子性。

class extensions很优雅的解决了这个问题。

具体来说,你可以像下面这样来声明一个property:

  1. @interface MyClass : NSObject  
  2. @property(readonly) NSView *targetView;  
  3. @end  


然后是实现文件:

  1. @interface MyClass()  
  2. @property(readwrite) NSView *targetView;  
  3. @end  
  4.   
  5. @implementation MyClass  
  6. @synthesize targetView;  
  7. @end  


这样一个publicly readonly、privately readwrite 的property就完成了。

你可能感兴趣的:(iOS开发)