OC学习笔记——协议与委托

什么是协议

协议类似于接口,用于定义多个类应遵守的规范。它不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它规定这批类中必须提供某些方法。
协议不提供任何实现,让规范和具体的实现分离,使整个项目逻辑更加清晰。
协议定义的是多个类的公共行为规范,这些行为是与外部交流的通道,所以协议里通常是一组共用方法,但不提供实现,具体方法的实现将在类中完成。

使用类别实现非正式定义

在学习类别与扩展时,我们知道类别可以实现非正式定义,这种类别以NSObject为基础,为NSObject创建类别,创建类别的时候就可以指定该类别应该新增的方法。
当某一个类实现NSObject的该类别时,就需要实现类别下的所有方法,这样的类别即是非正式协议。

下面这段程序以NSObject为基础,定义了一个类别,其名称为Eatable。

@interface NSObject (Eatable)

- (void)taste;

@end

下面这段程序为NSObject派生了一个子类,并满足了taste方法,
这样Apple类相当于遵守了Eatable协议,接下来就可以把Apple当Eatable对象来调用。

@interface Apple : NSObject

@end

@implementation Apple

- (void)taste {
    NSLog(@"苹果营养丰富");
}

@end

然后我们来测试一下:

#import <Foundation/Foundation.h>

@interface NSObject (Eatable)

- (void)taste;

@end

@interface Apple : NSObject

@end

@implementation Apple

- (void)taste {
    NSLog(@"苹果营养丰富");
}

@end

int main() {
    @autoreleasepool {
        Apple *apple = [[Apple alloc] init];
        [apple taste];
    }
}

输出:
在这里插入图片描述
需要注意,对于实现非正式协议的类来说,OC并不强制实现该协议中的全部方法,那些未实现的方法显然不能调用,不然就会引发错误。

正式协议的定义

与定义类不同,正式协议不再使用@interface、@implementation等关键字,而是使用@protocol关键字。

正式定义协议语法:

@protocol 协议名<父协议1,父协议2...> {
	//方法定义
}

需要注意:

  • 协议名应与类名采用相同的“大驼峰”命名规则,即名称由多个单词连接而成,每个单词首字母都大写
  • 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类
  • 协议中定义的方法只有方法签名,没有方法实现;协议中包含的方法可以是类方法,也可以是实例方法;协议里的所有的方法都是公开的访问权限

我们先定义两个协议Output和Productable:

@protocol Output

- (void)output;
- (void)addData:(NSString *)msg;

@end

@protocol Productable

- (NSDate *)getProduceTime;

@end

然后定义一个Printable协议,继承上面的两个协议:

@protocol Printable <Output, Productable>

- (NSString *)printColor;

@end

协议不同于类继承中的“树”关系,一个协议可以有多个直接的父协议,多个父协议在< >中以 , 隔开。

遵守(实现)协议

在类定义的接口部分可以指定该类继承的父类,及遵守的协议。

@interface 类名 : 父类 <协议1, 协议2...>

下面的程序,我们为Printble协议提供一个实现类Printer

#define MAX_CACHE_LINE 10

@interface Printer : NSObject <Productable>

@end

@implementation Printer {
	//使用数组缓存打印的数据
    NSString *printData[MAX_CACHE_LINE];
    //记录待打印的数据个数
    int dataNum;
}
//打印所有数据
- (void)output {
    while(dataNum > 0) {
        NSLog(@"打印机使用%@打印:%@", self.printColor, printData[0]);
        dataNum--;
        for (int i = 0; i < dataNum; i++) {
            printData[i] = printData[i + 1];
        }
    }
}
//增添数据
- (void)addData:(NSString *)msg {
    if(dataNum >= MAX_CACHE_LINE) {
        NSLog(@"输出队列已满");
    }else {
        printData[dataNum++] = msg;
    }
}
//获取当前时间
- (NSDate *)getProduceTime {
    return [[NSDate alloc] init];
}
//输出打印颜色
- (NSString *)printColor {
    return @"红色";
}

@end

在主函数中测试一下

int main() {
    @autoreleasepool {
        Printer *printer = [[Printer alloc] init];
        [printer addData:@"西邮"];
        [printer addData:@"移动应用"];
        [printer output];
        [printer addData:@"开发"];
        [printer addData:@"实验室"];
        [printer output];
        //创建一个Printer对象,当成Productable使用
        NSObject<Productable> *p = [[Printer alloc] init];
        //调用Productable协议中定义的方法
        NSLog(@"%@", p.getProduceTime);
        //创建一个Printer对象,当成Output使用
        id<Output> out = [[Printer alloc] init];
        //调用Output协议中定义的方法
        [out addData:@"欢迎你"];
        [out addData:@"!!!"];
        [out output];
    }
}

输出:
在这里插入图片描述
从程序中注释部分可以看出,我们可以用协议来定义变量,而且有两种方法:

  • NSObject<协议1, 协议2…> *变量名;
  • id<协议1, 协议2…> 变量名;

这样定义的变量,他们的编译时类型仅仅是遵守的协议类型,因此只能调用该协议中定义的方法。
 
 
对比正式协议与非正式协议,我们可以发现有许多差异:

  • 非正式协议通过NSObject创建类别来实现;正式协议直接使用@protocol创建
  • 遵守非正式协议通过继承带特定类别的NSObject来实现;遵守正式协议有专门的OC语法
  • 遵守非正式协议通过不要求实现协议中定义的所有方法;遵守正式协议必须实现协议中的所有方法

在正式协议中,可以通过以下两个关键字控制方法是否是必要的:

  • @aptional:位于该关键字后、@required或@end之前声明的方法是可选的
  • @required:位于该关键字后、@optional或@end之前的声明方法是必需的

协议与委托

协议体现的是一种规范,定义协议的类可以把协议定义的方法委托给实现协议的类,这样可以让类定义具有更好的通用性质,而将具体实现部分放到实现类去完成。

举个例子,A类中有个方法,对两个数进行操作,将结果输出到控制台。但其中并没有说明如何操作者两个数,而是在输出前调用了协议中的方法,让遵守协议的类去实现这个方法。这样的好处是,在不修改A类的情况下,就可以自由修改操作类型。

创建AClass并创建协议,为AClass定义delegate属性和数字操作完输出的方法

//AClass.h

//创建协议,定义协议名和父类
@protocol AClassDelegate <NSObject>
 
//@requierd为必须实现的方法
@required
- (int)doSomethingWithNumber1:(int)num1 andNumber2:(int)num2;

@end

@interface AClass : NSObject

//delegate属性
@property (nonatomic,weak) id<AClassDelegate> delegate;

- (void)logAnswerWithNumber1:(int)num1 andNumber2:(int)num2;

@end

实现输出功能

//AClass.m

#import <Foundation/Foundation.h>
#import "AClass.h"
 
@implementation AClass

- (void)logAnswerWithNumber1:(int)num1 andNumber2:(int)num2 {
    int answer = [self.delegate doSomethingWithNumber1:num1 andNumber2:num2];
    NSLog(@"%d", answer);
}

@end

在BClass中遵守该协议

//BClass.h

#import <Foundation/Foundation.h>
#import "AClass.h"

//遵守协议
@interface BClass : NSObject <AClassDelegate>
 
@end

实现协议具体方法(如两数相加)

//BClass.m

#import <Foundation/Foundation.h>
#import "BClass.h"
 
@implementation BClass
 
- (int)doSomethingWithNumber1:(int)num1 andNumber2:(int)num2 {
    return num1 + num2;
}
 
@end

控制台输出

//main.m

AClass *a = [[AClass alloc] init];
BClass *b = [[BClass alloc] init];
//b成为a的代理
a.delegate = b;
//调用a的方法
[a logAnswerWithNumber1:3 andNumber2:5];

输出:
在这里插入图片描述
之后对操作进行修改,只需在BClass中进行。这就达成了不改变AClass的情况下,对AClass的功能进行修改。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值