类别
通过使用类别,我们可以动态地为现有的类添加新方法,还可以将类定义模块化地分布到多个相关文件中。
类别同样由接口部分和实现部分组成,语法格式如下:
接口部分语法格式:
@interface 已有类(类别名)
//实现方法
...
@end
虽然该语法格式看起来很像在定义类,但定义类别的语法与定义类的语法存在很多差异,如下:
实现部分语法格式:
@implementation 已有类(类别名)
//方法实现
...
@end
下面是对NSNumber增加一个类别的代码实现。
接口部分:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//定义一个类别
@interface NSNumber (fk)
//在类别中定义4个方法
- (NSNumber*) jia: (double) num2;
- (NSNumber*) jian: (double) num2;
- (NSNumber*) cheng: (double) num2;
- (NSNumber*) chu: (double) num2;
@end
NS_ASSUME_NONNULL_END
实现部分:
#import "NSNumber+fk.h"
//为类别提供实现部分
@implementation NSNumber (fk)
//实现类别的接口部分定义的4个方法
- (NSNumber*) jia: (double) num2
{
return [NSNumber numberWithDouble: ([self doubleValue] + num2)];
}
- (NSNumber*) jian: (double) num2
{
return [NSNumber numberWithDouble: ([self doubleValue] - num2)];
}
- (NSNumber*) cheng: (double) num2
{
return [NSNumber numberWithDouble: ([self doubleValue] * num2)];
}
- (NSNumber*) chu: (double) num2
{
return [NSNumber numberWithDouble: ([self doubleValue] / num2)];
}
@end
主函数:
#import <Foundation/Foundation.h>
#import "NSNumber+fk.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber* a = [NSNumber numberWithInt: 3];
//测试jia:方法
NSNumber* jia = [a jia: 3.14];
NSLog(@"%@", jia);
//测试jian:方法
NSNumber* jian = [a jian: 1.1];
NSLog(@"%@", jian);
//测试cheng:方法
NSNumber* cheng = [a cheng: 1.1];
NSLog(@"%@", cheng);
//测试chu:方法
NSNumber* chu = [a chu: 2.0];
NSLog(@"%@", chu);
}
return 0;
}
运行结果:
不同写法:
#import <Foundation/Foundation.h>
#import "NSNumber+fk.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSNumber* a = [NSNumber numberWithInt: 3];
//测试jia:方法
NSNumber* jia = [a jia: 3.14];
NSLog(@"%lf", [jia doubleValue]);
//测试jian:方法
NSNumber* jian = [a jian: 1.11];
NSLog(@"%lf", [jian doubleValue]);
//测试cheng:方法
NSNumber* cheng = [a cheng: 1.11];
NSLog(@"%lf", [cheng doubleValue]);
//测试chu:方法
NSNumber* chu = [a chu: 2.0];
NSLog(@"%lf", [chu doubleValue]);
}
return 0;
}
运行结果:
虽然类别可以重写原有类中的方法,但通常不建议这么做,如果需要重写原有类的方法,更好的建议是通过原有类派生子类,然后在子类中重写父类原有的方法。
关于类别的两点说明和三种用法:
利用类别对类进行模块化设计
在前面类的设计中,可以使用*.h 文件来定义类接口部分,使用*.m 文件来定义类实现部分,但不能将类实现部分分布到多个*.m 文件中。当某个类非常大时,如果将该类的所有实现代码放在一个文件中,将会导致这个文件非常大,以至于维护起来非常困难。
如果需要,可以使用类别将一个较大的类进行分模块设计,以便于进行代码的维护。
使用类别来调用私有方法
在前面的学习中我们知道,在实现部分定义的方法相当于私有方法,通常不允许调用。但是实际上,OC并没有真正的私有方法,使用NSObject的performSelector:方法来执行动态调用,就可以调用那些私有方法。
除了使用performSelector:方法来执行动态调用私有方法外,我们还可以通过类别来定义前向引用,从而实现私有方法的调用。
代码示例如下:
接口部分:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKItem : NSObject
@property (nonatomic, assign) double price;
- (void) info;
@end
NS_ASSUME_NONNULL_END
实现部分:
#import "FKItem.h"
@implementation FKItem
@synthesize price;
//实现接口部分定义的方法
- (void) info
{
NSLog(@"这是最开始的info方法");
}
//额外新增的方法(即私有方法)
- (double) calDiscount: (double) discount
{
return self.price * discount;
}
@end
该实现部分的新增方法即为私有方法,若我们直接通过FKItem对象调用calDiscount:方法必然会出错,为了能在程序中正常调用calDiscount:方法,我们可以在main()函数前面加上一个类别定义,完整主函数部分如下:
#import <Foundation/Foundation.h>
#import "FKItem.h"
//为FKItem定义一个类别
@interface FKItem (fk)
//在类别中前向声明calDiscoun:方法
- (double) calDiscount: (double) discount;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKItem* item = [[FKItem alloc] init];
item.price = 100;
[item info];
NSLog(@"物品打折后的价格为:%g", [item calDiscount: 0.75]);
}
return 0;
}
运行结果:
扩展
扩展与类别相似,定义扩展的语法格式如下:
@interface 已有类 ()
{
实例变量
}
//方法定义
...
@end
从语法上来看,扩展相当于定义一个匿名的类别,但从用法来看,类别一般是有单独的*.h和*.m文件,扩展则用于临时对某个类的接口进行扩展,类实现部分同时实现类接口部分定义的方法和扩展中定义的方法。
定义类的扩展的时候,可以额外增加实例变量,也可以用@property来合成属性(包括setter和getter方法和对应的成员变量),但定义类的类别的时候,是不允许额外定义实例变量和合成属性的。
代码示例:
接口部分:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKCar : NSObject
@property (nonatomic,copy) NSString *brand;
@property (nonatomic,copy) NSString *model;
- (void) drive;
@end
NS_ASSUME_NONNULL_END
扩展部分新建*.h文件时,一般建议在File处写成扩展类别名,在Class部分写成被扩展类,在File Type部分记得选择Extension。
扩展的接口部分:
#import <Foundation/Foundation.h>
#import "FKCar.h"
NS_ASSUME_NONNULL_BEGIN
//定义FKCar的扩展
@interface FKCar ()
@property (nonatomic, copy) NSString* color;
- (void) drive: (NSString*) owner;
@end
NS_ASSUME_NONNULL_END
实现部分:
#import "FKCar.h"
#import "FKCar+drive.h"
@implementation FKCar
@synthesize brand;
@synthesize model;
@synthesize color;
- (void) drive {
NSLog(@"汽车正在路上跑");
}
- (void) drive: (NSString*) owner {
NSLog(@"%@正驾驶着%@汽车在路上跑", owner, self);
}
- (NSString*) description {
return [NSString stringWithFormat:@"<FK[brand = %@,model = %@,color = %@", self.brand, self.model, self.color];
}
@end
主函数:
#import <Foundation/Foundation.h>
#import "FKCar+drive.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建FKCar对象
FKCar* car = [[FKCar alloc] init];
//使用点语法为car对象的属性赋值
car.brand = @"宝马";
car.model = @"X5";
car.color = @"黑色";
//调用car方法
[car drive];
[car drive: @"孙悟空"];
}
return 0;
}
运行结果: