[Objective-C]今天是正经人-类别与扩展与协议
文章目录
类别与扩展
有的时候希望为NSNumber 类新增一些方法
但由于NSNumber 实际上只是一个类族(cluster)的前端类
例如,
通过 [NSNumber numberWithInt: 5] 方法所生成的 NSNumber 对象其实只是NSNumber子类的实例
这样即使想要通过继承为 NSNumber 派生子类也没有任何意义
派生的子类对 NSNumber 现有的子类并没有任何影响
此时就需要借助类别 (category) 来实现
怎么定义类别来添加方法
Objective-C 的动态特征允许使用类别为现有的类添加新方法
不需要创建子类也不需要访问原有类的源代码。
通过使用类别,即可动态地为现有的类添加新方法,而且可以将类定义模块化地分布到多个相关文件中
类别同样由接口和实现部分组成
来个类别的接口部分
@interface NSNumber (fk) // 在类别中定义4个方法
- (NSNumber*) add: (double)num2;
- (NSNumber*) substract: (double)num2;
- (NSNumber*) multiply: (double)num2:
- (NSNumber*) divide: (double)num2;
@end
来个类别的实现部分
@implementation NSNumber (fk) // 实现类别接又部分定义的4个方法
- (NSNumber*) add: (double)num2 {
return [NSNumber numberWithDouble: ([self doubleValue] + num2)];
}
- (NSNumber*) substract: (double)num2 {
return [NSNumber numberWithDouble: ([self doubleValuel - num2)]:
}
- (NSNumber*) multiply: (double)num2 {
return [NSNumber numberWithDouble: ([self doubleValuel * num2)];
}
- (NSNumber*) divide: (double)num2 {
return (NSNumber numberWithDouble: ((self doubleValue] / num2)];
}
@end
上面为NSNumber 定义了长类别,接下来只要在程序中导入 NSNumber+ fk.h 头文件
在测试程序中使用NSNumber 类,该类的实例就会具有add:、substract: 、multiply: 和divide:方法
这就实现了对原有NSNumber 类的动态扩展
虽然类别可以重写原有类中的方法,但通常并不推荐这么做
如果需要重写原有类的方法
更好的建议是通过原有类派生子类,然后在子类中重写父类原有的方法
关于类别, 还有如下两点说明
- 通过类别为指定类添加新方法之后,这个新方法不仅会影响NSNumber 类,还会影响 NSNumber 类的所有子类,每个子类都会获取类别扩展的方法。
- 可根据需要为一个类定义多个类别,不同的类别都可对原有的类增加方法定义。
类别是 Objective-C 中一个非常重要的知识,它通常有如下了种用法
- 利用类别对类进行模块化设计
- 使用类别来调用私有方法
- 使用类别来实现非正式协议
别急,下面就来介绍利用类别对类模块化设计
当某个类非常大时,如果将该类的所有实现代码放在一个文件中将会导致这个文件非常大,以至于维护起来非常困难
如果需要将一个较大的类分模块设计,使用类别是一个不错的选择
//在 NSWindow.h 头文件中可以看到 NSWindow 的定义
//在 Xcode 中按住 command 键
//在 NSWindow 类上单击鼠标打开 NSWindow 的 NSWindow.h 头文件
@interface NSWindow : NSResponder<NSAnimatablePropertyContainer,
NSUserInterfaceValidations, NSUserInterfaceItemIdentification>
除了上面的接口定义之外,还可以看到如下类别:
@interface NSWindow (NSKeyboardUI)
@interface NSWindow (NSToolbarSupport)
@interface NSWindow (NSDrag)
@interface NSWindow (NSCarbonExtensions)
通过这种方式,NSWindow 可以分别提供多个实现文件,通过这种方式就可以对类实现按模块分布到不同的*. m 文件中,从而提高项目后期的可维护性
靠前向引用调用私有方法
前面在介绍定义类时提到
没有在接口部分定义而是在类实现部分定义的方法相当于私有方法,通常不允许被调用
但后面也说过,Objective-C 实际上并没有真正私有的方法
如果使用NSObject 的 performSelector: 方法来执行动态调用 则完全可以调用那些私有方法。
但如果使用performSelector: 来执行调用, 则完全避开了编译器的语法检查
除了使用 performSelector: 方法来动态调用那些私有方法之外
还可以通过类别来定义前向引用,从而实现对私有方法的调用
。
来看接口代码。
#import <Foundation/Foundation.n>
// 定义类的接口部分
@interface FKItem : NSObject
@property(nonatomic , assign) double price; - (void) info;
@end
来看实现部分
#import "FKItem.h"
//为FKItem提供实现部分
@implementation FKItem
@synthesize price;
//实现接口部分定义的方法
- (void)info {
NSLog(@"这是一个普通的方法");
}
//类实现部分新增的方法,相当于私有方法
- (double)calDiscount: (double)discount {
return self.price * discount;
}
@end
上面的是 FKltem 实现部分额外新增的方法
这就是Objective-C所谓的“ 私有方法”
一般直接通过 FKltem 对象调用 calDiscount: 方法必然导致错误
<Foundation/Foundation.h>
#import "FKItem.h"
int main(int argc , char *argv[])
@autoreleasepool {
FKItem* item = [[FKItem alloc] init];
item.price = 109;
[item info]:
NSLog(@”物品打折的价格为:%g" , [item calDiscount :. 75]);
}
}
上面程序中调用了 FKltem 对象的 calDiscount : 方法
但这个方法仅在 FKltem 类的实现部分定义,接口部分并没有定义这个方法
尝试编译该程序,将可看到编译器显示如下提示:
FKItemTest.m:13:45: error: no visible @interface for “FkItem•
declares the selector ‘calDiscount:’ NSLog(@”物品打折的价格为:%g” , [item calDiscount :. 75]);
从上面的错误提示可以看出,编译器会提示:FKltem 对象不能调用 calDiscount: 方法 (Objective-C 编译器习惯把方法称为selector )。
为了能在该程序中正常调用 calDiscount: 方法,程序可以在该 main0 函数前增加如下类别定义:
//为FKItem定义一个类別
@interface FKItem (fk)
//在类别中声明 calDiscount: 方法
- (double)calDiscount: (double)discount;
@end
这样在 main 函数前定义了该类别之后,FKltem 的长类别中自然就增加 了calDiscount: 方法
这样编译器就不再提示上面所示的错误了
扩展相当于定义匿名类别
定义扩展的语法格式如下:
@interface 己有类 () {
实例变量
}
// 方法定义
@end
就用法来看,类别通常有单独的*.h 和*.m 文件
扩展则用于临时对某个类的接口进行扩展
类实现部分同时实现 类接口部分定义的方法和扩展中定义的方法
在定义类的扩展时,可以额外增加实例变量,也可以使用@property 来合成属性 (包括 setter 、getter 方法和对应的成员变量)
但定义类的类别时,则不允许额外定义实例变量,也不能用 @property 合成属性
协议与委托
许多人认为接口等同于主机板上的插槽,这其实是一种错误的认识 当提到 PCI 接口时,指的是主机板上那条插槽遵守了PCI
规范,而具体的PCI 插槽只是PCI 接口的实例 对于不同型号的主机板而言,它们各自的PCI
插槽都需要遵守一个规范,遵守这个规范就可以保证插入该插槽里的板卡能与主机板正常通信。 对于同一个型号的主机板而言,它们的PCI
插槽需要有相同的数据交换方式和相同的实现细节,它们都是同一个类的不同实例
非正式协议还有正式协议
协议定义了一种规范,协议定义某一批类所需要遵守的规范,只规定这批类中必须提供某些方法,提供这些方法的类就可满足实际需要
协议不提供任何实现。协议体现的是规范和实现分离的设计哲学。让规范和实现分离正是协议的好处
协议定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着协议里通常是定义一组公用方法,但不会为这些方法提供实现,方法的实现则交给类去完成
非正式协议
当某个类实现 NSObject 的该类别时,就需要实现该类别下的所有方法,这种基于 NSObject 定义的类别即可认为是非正式协议
看个Eatable类别的例子
#import <Foundation/Foundation.h>
//以Nsobject为基础定义Eatable类别
@interface NSObiect (Eatable)
- (void)taste;
@end
上面在 NSObject 的Eatable 类别中定义了 一个taste 方法,接下来所有继承 NSObject 类的子类都会自动带有该方法,而且NSObject 的子类可以根据需要,自行决定是否要实现该方法,遵守该协议的子类通常都会实现这 个方法。
需要指出的是,对于实现非正式协议的类而言,Objective-C 并不强制实现该协议中的所有方法,上面的FKApple 类也可以不实现taste 方法;
但如果类不实现 taste 方法,而且非正式协议本身也没有实现该方法,那么运行该程序就会引起 unrecognized selector 错误。
正式协议
和定义类不同,正式协议不再使用@interface .@implementation 关键字,而是使用@protocol 关 键 字。 定 义 正 式 协 议 的 基 本 语 法 格 式 如 下
@protocol 协议名<父协议1,父协议2> 零个到多个方法定义
- 协议名应与类名采用相同的命名规则
- 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类
- 协议中定义的方法只有方法签名,没有方法实现
- 协议中包含的方法既可是类方法,也可是实例方法
- 协议里所有的方法都具有公开的访问权限。
看个正式协议吧
#import<Foundation/Foundation.h>
// 定义协议
@protocol FKProductable
// 定义协议的方法
- (NSDate*)getProduceTime;
@end
接下来定义一个打印机协议,该协议继承上面的协议
#import <Foundation/Foundation.h>
#import "FkProductable.h"
// 定义协议,继承了 FKProductable 协议
@protocol FKPrintable <FKProductable>
// 定义协议的方法
- (NSString*) printcolor;
@end
协议的继承和类继承不一样,协议完全支持多继承 ,即一个协议可以有多个直接的父协议。 和类继承相似, 子协议继承某个父协议,将会获得父协议里定义的所有方法。
一个协议继承多个父协议时,多个父协议排在 <> 中间,多个协议之间以英文逗号隔开。
遵守协议或者称实现协议
在类定义的接口部分可指定该类继承的父类,以及遵守的协议
语 法 格 式 如 下:
@interafce 类名: 父类 < 协议1, 协议2 . . . >
从 上面的语法格式可以看出,一个类可以同时遵守多个协议
看个接口部分代码
#import <Foundation/Foundation.h>
#import "FKPrintable.h"
// 定义类的接口部分,继承 NSObject,遵守 FKPrintable 协议
@interface FKPrinter : NSObiect <EKPrintable>
@end
实现部分的代码
#import "FKPrinter.h"
#define MAX_CACHE_LINE 10
// 为 FKPrinter 提供实现部分 Cimplementation FKPrinter
NString* printData[MAX_CACHE_LINE];
//使用数组记录所有需要缓存的打印数据 int dataNum;
//记录当前需打印的作业数
- (void)output {
//只要还有作业,就继续打印 while (dataNum> 0)
NSLog(@“打印机使用号@打印:%@" ,self.printcolor , printDate[0]);
//将剩下的作业数减1
dataNum--;
//把作业队列整体前移一位
for(int i= 0; i < dataNum; i++) {
printData[i] = printData[i + 1];
}
}
- (void)addData: (NSString*) msg {
if (dataNum => MAX CACHE LINE) {
NSLog( @“ 输 出 队 列 己 满 , 添 加 失 败 " );
} else {
//把打印数据添加到 列里 ,己保存数据的数量加1
printData [dataNum++] = msg;
}
}
- (NSDate*)getProduceTime {
return [[NSDate alloc] init];
}
ー (NSString*)printColor {
return @"红色";
}
@end
如果实现类实现了协议中的所有方法,那么程序就可以调用该实现类所实现的方法
本篇小总结
- 通过定义类别来为类添加新的方法
- 类别三大功能之一的调用私有方法
- 相当于匿名类别但又有差别的扩展
- 如何去定义非正式协议和正式协议
- 定义完协议后需要去实现你的协议