类别(Categories)和扩展/匿名类别(extensions)及其延伸使用

类别(Category)
 
分类能够做到的事情主要是:即使在你不知道一个类的源码情况下,向这个类添加扩展的方法
 
此外,类别能够保证你的实现类和其他的文件区分开。
 
1 #import “UIViewController.h”
2 @interface UIViewController(CustomView)
3 -(void)extMethod;
4 @end

 

 使用类别为类添加方法(Add Methods to Classes)
 
通过在interface中声明一个额外的方法并且在implementation 中定义相同名字的方法即可。分类的名字(也就是括号括起来的CustomView)表示的是:对于声明于其他地方的这个类(UIViewController),在此处添加的方法是额外的,而不是表示这是一个新的类你不可以通过分类为一个类添加额外的成员变量
 
在implementation中,引入头文件的时候主要引用的方式是:
 
1 #import “UIViewController+CustomView.h”
2 @implementation UIViewController(CustomView)
3 -(void)extMethod;
4 @end

 

 另外,虽然Category不能够为类添加新的成员变量,但是Category包含类的所有成员变量,即使是@private的。Category可以重新定义新方法,也可以override继承过来的方法。

  关于Category不能添加成员变量或调用@synthesize的原因:
在类中@property关键字会生成私有成员变量,和申明getter  setter方法,但是在分类中只会声明getter和setter方法,不会生成成员变量(因为变量需要存储,而在category里不能有效的声明一个存储区)。
所以、严格来说.Category中的@property代表的是真正意义上的属性(属性其实是set和get方法、而对应变量并非必须存在)
此外:如果在.m中手动实现了setter和getter方法,x-code就不会自动生成@synthesize,也就不会生成成员变量点语法访问的是getter方法,对象的->调用才是访问的成员变量,如果我们真的需要给category增加变量的实现,需要借助于运行时(runtime)的两个函数来生成模拟变量
objc_setAssociatedObject
objc_getAssociat
edObject 


//NSObject+IndieBandName.h
@interface NSObject (IndieBandName)
@property (nonatomic, strong) NSString *indieBandName;
@end

上面是头文件声明,下面的实现的.m文件:

复制代码
// NSObject+IndieBandName.m    
#import "NSObject+Extension.h"
#import <objc/runtime.h>
static const void *IndieBandNameKey = &IndieBandNameKey;    
@implementation NSObject (IndieBandName)
@dynamic indieBandName;

- (NSString *)indieBandName {
    return objc_getAssociatedObject(self, IndieBandNameKey);
}

- (void)setIndieBandName:(NSString *)indieBandName{
    objc_setAssociatedObject(self, IndieBandNameKey, indieBandName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
复制代码

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
延伸使用方法举例与注意事项:

1、扩展一个其它实施者定义的类
   例如,你可以为Cocoa frameworks里的类增加方法。增加的方法会被子类继承、而且在运行时也不会和原始的方法有任何不同。

2、作为子类的一个替代方式
   不需要定义一个子类来扩展已有的类,通过category你可以直接为类添加方法。
  例如,你可以为NSArray和其它的Cocoa classes添加categories.与添加子类的方式来比,你不需要你扩展的类的源代码。

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

   1.提供一个简单的方式来组合相关的方法。被定义在不同的类里的相似的方法可以被保存在同一个源文件里。

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

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

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

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

4、可以用来声明非正式协议
   例如:@interface NSObject ( MyXMLSupport )

- initFromXMLRepresentation: (NSXMLElement *)XMLElement;

- ( NSXMLElement *)XMLRepresentation;

      @end
以上、由于是非正式协议。所以编译器不会检测协议中的语法、以及方法是否实现

5、root class类别

Category 可以为任何的类添加方法,其中也包括root class。添加到NSObject类上的方法对于所有与你的代码相关联的类都是可用的。有时候为root class添加方法是非常有用的,但是它也是非常危险的。虽然从表面上看起来category所做出的修改可以被很好的理解,而且影响也是有限的,但是继承的机制使得它有了一个广泛的作用域。你可能会对你程序里不可见的类做出意想不到的修改;你可能会对你正在做的事会产生的结果一无所知。甚者,当对你修改过什么一无所知的人在你的程序上工作时,他们对于他们正在做的事也不会有一个充分的了解。

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

发送消息给super是非法的(因为NSObject没有超类

类的对象可以执行root class中定义的实例方法

正常来说,类对象只能执行类方法。但是root class中定义的实例方法是一个特例。它们定义了一个类,在运行时系统中的所有对象都继承这个类。类对象是完全成熟的对象,它需要共享同一个类。这个特性意味着你为NSObject类在category定义的实例方法不仅要能被实例对象执行,而且也要能被类对象执行。例如:在方法体中,self可能代表一个类对象,也可能是类的一个实例。



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

1.当category 重载一个从父类继承过来的方法,通常可以通过super关键字来调用父类的实现方法。然而,如果category重载一个扩展类本身存在的方法,就没有唤醒原始实现方法的办法了。
 
2.同一个类的category不能声明重载这个类的另一个category中声明的方法。
  这一点非常的重要,因为很多Cocoa类也是通过使用categories来实现的。 一个你试图重载的框架中定义的方法可能本身就已经在一个category被实现了, 如果你这样做了,很可能使用得前面的category的方法的实现失效。

3.一些category methods的存在可能会导致整个框架的行为发生变化。
   例如,如果你在NSObject的一个category中重载windowWillClose:委托方法,在你的程序里所有窗口的委托将会使用category方法来回应;所有NSWIndow实例的行为都会改变。你为一个框架类增加的Categories可能会导致行为上很神秘
的变化和程序的崩溃。


类扩展(Class Extensions)
 
类扩展就像匿名(也就是没有那个括号里面的名字CustomView)的分类一样,除了一样不同的是,类扩展声明必须在相应的主@implementation代码块中实现。
 
先看一段代码:
复制代码
 1 @interface MyObject:NSObject
 2 {
 3 NSNumber* number;
 4 }
 5 -(NSNumber*)getNum;
 6 @end
 7  
 8 @interface MyObject(Setter)
 9 -(void)setNum:(NSNumber*)num;
10 @end
11  
12 @implementation MyObject
13 -(NSNumber*)getNum
14 {
15 return number;
16 }
复制代码

 

 看上面这段代码,有没有问题?编译器编译的时候,这段代码是可以编译通过(有警告),但当运行时,就会报错。为什么?
因为没有实现Category中的setNum方法。

而用类扩展去实现,请看:
复制代码
 1 @interface MyObject:NSObject
 2 {
 3 NSNumber* number;
 4 }
 5 -(NSNumber*)getNum;
 6 @end
 7  
 8 @interface MyObject() //注意这里的括号里面是没有名字的
 9 -(void)setNum:(NSNumber*)num;
10 @end
11  
12 @implementation MyObject
13 -(NSNumber*)getNum
14 {
15 return number;
16 }
17  
18 -(void)setNum:(NSNumber*)num
19 {
20 number = num;
21 }
22 @end
复制代码

 

setNum是要实现的,不然编译器会提出警告。
 
从上面看出,分类和类扩展的相似之处是:都可以为类添加一个额外的方法、如未实现都会出现提示
 
不同之处在于:要添加额外方法,类别必须在第一个@interface中声明方法,并且在相关的某个@implementation中提供实现(即使在主@implementation中实现、而未在类别的.m中未实现也会出警告、不过不影响)。而类扩展,方法的声明可以不在第一个@interface中去声明。(这里特别指出、如果无法获悉一个类的源码,用扩展添加方法是不可取的。因为extension只有.h文件,没有自己的@implementation,必须到源码的主@implementation模块去实现方法)

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

关于Extensions的延伸用法

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

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


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

当实现一个类,通常在类的@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中声明的方法的实现,编译器将会发出抱怨。

2、设计一个对外只读、对内可读可写的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就完成了。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值