前言
本篇文章介绍OC的分类和协议
分类
分类是这样一种设计,对于一个已经存在的类A,这个类可能是自己写的,或者是第三方的,甚至是系统提供的。这个类对我们很有用,但是,我们有些想要的功能在这个类里却没有实现。这个时候我们可以使用分类的功能对类A进行扩展。使用语法如下:
// 这个是A.h文件,定义了A的功能
@interface A : NSObject
{
// 定义了一个实例变量,可以被子类使用
int iA1;
}
@property int pA1;
-(void) A1;
@end
// 这是是分类A+B.h文件
#import "A.h"
// 分类的写法是这样的,主类名(分类名)
// 必须import主类名
@interface A(B)
{
int iA_B1;
}
@property int pA_B1;
-(void)A_B1;
@end
分类完成后,如果我们想使用分类,必须import 分类的头文件
#import <Foundation/Foundation.h>
#import "A+B.h"// 这里需要包含分类名的头文件
int main(int argc, const char * argv[]) {
@autoreleasepool {
A* a = [[A alloc] init];
[a A_B1];// 可以看到,A的实例可以调用分类的方法
}
return 0;
}
注意,我们在定义分类B的时候不需要列出类A继承的类。
分类有如下特性:
- 如果我们在分类中定义属性,分类不会自动生成访问器方法。编译器会给出警告,并且如果我们调用访问器方法,运行会出现异常
- 如果我们需要定义只读的属性,需要在属性定义时使用
readonly
限定符,这样就不会警告了。 - 分类可以使用主类在接口中定义的实例变量,但是不可以使用主类在实现中定义的实例变量,因为那是私有的。这很好理解,我们如果给系统类实现一个分类,我们总不能访问系统类的私有实例变量吧。
- 分类不能在接口中定义实例变量,但是可以在实现中定义,仅仅当前分类使用
分类的命名
如果我们需要将分类单独保存到一个文件,通常采用A+B的形式,其中A是主类名,B是分类名
类的扩展
有一种特殊的情况是创建一个未命名的分类,实现方法就是在定义分类时在括号中不指定名字。这种特殊的语法称为类的扩展,类的扩展一般是定义在主类的实现文件中,下面是类扩展的一个例子
// A.h文件
@interface A : NSObject
{
int iA1;
}
@property int pA1;
-(void)A1;
@end
// A.m文件
#import "A.h"
// 这是类A的扩展
@interface A()
{
int iA_1;
}
@property int pA_1;
-(void)A_1;
@end
@implementation A
-(void)A1
{
NSLog(@"print A1");
}
-(void)A_1
{
NSLog(@"print A_1");
}
@end
如果我们在类的实现中定义类的扩展,扩展类有下面的属性:
- 类的扩展中可以定义属性和实例变量,但是
类的扩展中定义的属性,实例变量和方法都是私有的,只能在当前类内部访问,包括子类
。通过这种方式,我们可以定义私有的东西。 - 类的扩展中定义的方法必须在主类中实现,也就是说类扩展没有自己的实现部分
- 不要在分类或者扩展中覆盖主类中的方法,如果需要覆盖,建议使用继承
- 使用类扩展不仅会影响该类的所有用户,还会影响该类所有子类的用户。
协议和代理
协议的定义
协议是一组方法的接口
协议采用下面的格式进行定义
@protocol 协议名称
// 这里定义协议的接口
@required
@optional
@end
协议的使用
如果要在一个类中使用协议:
- 首先要继承定义协议的类或者包含协议定义的头文件。
- 并且把要遵守的协议名称放在类名和类继承的基类名称的后面,用<>括起来。
- 如果采用多个协议,只需要将协议名都放进尖括号,用逗号分隔即可
看下面的例子:
首先是定义协议,下面的协议定义在A.h中
@protocol CALDelegate
@required
-(int)add:(int) a with:(int) b;
-(int)sub:(int) a with:(int) b;
-(int)times:(int) a with:(int) b;
-(int)div:(int) a with:(int) b;
@optional
-(int)add1:(int) a with:(int) b;
-(int)sub1:(int) a with:(int) b;
-(int)times1:(int) a with:(int) b;
-(int)div1:(int) a with:(int) b;
@end
注意:
@required
:对于遵守协议名的类来说必须实现的方法列表@optional
:对于遵守协议名的类来说可以选择实现的方法列表
下面是遵守协议的类,有两种方式,一种通过继承:
@interface A1 : A<CALDelegate>
另一种通过包含A.h头文件
#import "A.h"
@interface C : NSObject<CALDelegate>
注意:协议是无类的,谁都可以遵守协议。
是否遵循协议
可以使用conformsToProtocol:
方法检查一个对象是否遵循某项协议,看下面例子:
A1* a = [[A1 alloc] init];
if([a conformsToProtocol:@protocol(CALDelegate)])
{
NSLog(@"A1 conform Protocol CALDelegate");
}
注意:
- 该方法只检查类是否遵循协议,并不代表该类实现了该协议下的方法
- 事实上,就算你不实现任何协议定义的方法,只要遵循协议,也可以对该协议下的方法进行调用。但是运行的时候会报错,就算是@required方法,编译器也仅仅给出警告而已。
所以,一般情况下,我们如果调用协议下的方法,需要提前判断当前类是否已经实现了该方法。
代理
代理是协议的一种高级用法,通常的使用方式如下:
- 在定义协议的类中定义一个代理类属性,该属性决定了代理类的性质和遵循的协议,看下面的例子:
其中// A.h @protocol CALDelegate @required -(int)add:(int) a with:(int) b; -(int)sub:(int) a with:(int) b; -(int)times:(int) a with:(int) b; -(int)div:(int) a with:(int) b; @optional -(int)add1:(int) a with:(int) b; -(int)sub1:(int) a with:(int) b; -(int)times1:(int) a with:(int) b; -(int)div1:(int) a with:(int) b; @end @interface A : NSObject { int iA1; } @property int pA1; @property NSObject<CALDelegate>* delegate; -(void)A1; -(void)print:(int)a with:(int)b with:(int)type; @end
这行代码就是定义代理类的属性,说明代理类需要是NSObject或者子类,并且遵循CALDelegate协议@property NSObject<CALDelegate>* delegate;
- 在代理类中注册
// C.m @implementation C -(void)print { A* a= [[A alloc] init]; if(!a.delegate) { // 代理类注册 a.delegate = self; } // 在协议类中就可以使用代理类实现的协议方法了 [a print:1 with:1 with:1]; [a print:1 with:1 with:2]; [a print:1 with:1 with:3]; [a print:1 with:1 with:4]; } // 后面是对协议方法的实现 // ...