Objective-C学习 协议和委托

文章目录

协议是Objective-C的一个重要知识点, 其作用类似于接口, 用于定义多个应该遵循的规范

协议

规范. 协议与接口

同一个类的内部状态数据、各种方法的实现细节完全相同,类是一种具体的实现体。而协议则定义了一种规范,协议定义某一批类所需要遵守的规范,它不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类中必须提供某些方法,提供这些方法的类就可满足实际需要。

协议不提供任何实现。协议体现的是规范和实现分离的设计哲学.

协议定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着协议里通常是定义一组公用方法,但不会为这些方法提供实现,方法的实现则交给类去完成

使用类别实现非正式协议

类别(category)可以实现非正式协议, 这种类别以NSObject 为基础, 为NSObject 创建类别, 创建类别时即可指定该类别应该新增的方法

当某个类实现NSObject的该类别时, 就需要实现该类别下所有的方法, 这种基于NSObject定义的类别即可认为是非正式协议

objc 复制代码
// 以NSObject为基础定义Eatable类别,作为非正式协议
@interface NSObject (Eatable)
- (void)taste;
@end
    
// 分类的实现(此处仅声明方法,无默认实现,由子类自行实现)
@implementation NSObject (Eatable)
// 非正式协议的方法无需在此实现,留给子类重写
@end

上面在 NSObject 的 Eatable 类别中定义了一个 taste 方法,接下来所有继承 NSObject 类的子类都会自动带有该方法,而且 NSObject 的子类可以根据需要,自行决定是否要实现该方法。当然,既然 Eatable 类别作为一个非正式协议使用,那么相当于定义了一个规范,因此,遵守该协议的子类通常都会实现这个方法。

objc 复制代码
// 定义FKApple类,继承NSObject,遵循Eatable非正式协议
@interface FKApple : NSObject
@end

@implementation FKApple
// 实现Eatable非正式协议中的taste方法
- (void)taste {
    NSLog(@"苹果营养丰富,口味很好!");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建FKApple对象并调用taste方法
        FKApple *apple = [[FKApple alloc] init];
        [apple taste];
        // 演示:调用前检查方法是否实现
        if ([apple respondsToSelector:@selector(taste)]) {
            [apple taste];
        } else {
            NSLog(@"FKApple未实现taste方法");
        }
    }
    return 0;
}
  1. [apple respondsToSelector:@selector(taste)]
    • respondsToSelector:NSObject协议的方法,作用是检查调用对象(这里是apple)是否实现了指定的选择器(方法)
    • @selector(taste)是将方法名taste转换成 Objective-C 中的选择器(SEL类型),用来标识这个方法。
    • 整行的返回值是BOOL类型:YES表示apple实现了taste方法,NO则表示未实现。

虽然 Objective-C 编译器并不强制遵守非正式协议的类必须实现该协议中所有的方法,但如果该类没有实现非正式协议中的某个方法,那么程序运行时调用该方法,就会引发 unrecognized selector 错误。

正式协议的定义

和定义类不同, 正式协议不再使用@interface , @implementation 关键字, 而是使用@protocol 关键字, 定义正式协议的基本语法格式如下:

objective-c 复制代码
@protocol 协议名<父协议1 : 父协议2> {
    零到多个方法定义 ...
} 

对上面语法格式的详细说明如下。

  1. 协议名应与类名采用相同的命名规则。即如果仅从语法角度来看,协议名只要是合法的标识符即可;如果要遵守 Objective-C 可读性规范,则协议名应由多个有意义的单词连接而成,每个单词首字母大写,单词与单词之间无须任何分隔福符。
  2. 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类
  3. 协议中定义的方法只有方法签名,没有方法实现;协议中包含的方法既可是类方法,也可是实例方法。

协议定义的是多个类共同的公共行为规范, 因此, 协议里所有的方法都具有公开的访问权限

定义一个协议

objc 复制代码
 @protocol FKOutput
// 定义协议的方法
- (void) output;
- (void) addData : (NSString*) msg;
@end
objc 复制代码
@protocol FKProductable
- (NSDate*) getProduceTime;
@end

接下类定义一个打印机协议, 该协议同时继承上面的两个协议

objc 复制代码
@protocol FKPrintable <FKOutput, FKProductable>
- (NSString*) printColor;
@end

协议的继承和类的继承不一样, 协议完全支持多继承, 即一个协议可以有多个直接的父协议. 和类继承相似, 自协议继承某个父类协议, 将会获得父协议里定义的所有方法.

一个协议继承多个父协议时, 多个父协议排在<>之间, 多个协议之间以英文逗号( , )隔开

遵守 (实现) 协议

在类定义的接口部分可指定该类继承的父类, 以及遵守的协议, 和语法格式如下;

objc 复制代码
@interface 类名: 父类 <协议1, 协议2...>

一个类可以同时遵守多个协议

由于Objective-C协议的功能基本等同于接口, 因此, 有时候也把遵守协议说成实现协议, 实际上, Objective-C编译器也使用implement作为实现协议的说法.

objc 复制代码
#import <Foundation/Foundation.h>
#define MAX_CACHE_LINE 10
@protocol FKOutput
// 定义协议的方法
- (void) output;
- (void) addData : (NSString*) msg;
@end


@protocol FKProductable
- (NSDate*) getProduceTime;
@end

@protocol FKPrintable <FKOutput, FKProductable>
- (NSString*) printColor;
@end

@interface FKPrinter : NSObject <FKPrintable>   // 遵守FKPrintable 协议
@end

// 为 FKPrinter 提供实现部分
@implementation FKPrinter {
    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 @"red";
}
@end

如果类实现定义了协议中的所有方法, 那么程序就可以调用该实现类所有实现的方法

objc 复制代码
int main(int argc, char* argv[]) {
    @autoreleasepool {
        FKPrinter* printer = [[FKPrinter alloc] init];
        // 调用FKPrinter 对象的方法
        [printer addData: @"hello"];
        [printer addData: @"hello"];
        [printer output];
        [printer addData:@"hello"];
        [printer addData: @"hello2"];
        [printer output];
        // 创建一个FKPrinter对象, 当成FKProductable 使用
        NSObject<FKProductable>* p = [[FKPrinter alloc] init];
        // 调用FKProductable协议中定义的方法
        NSLog(@"%@", p.getProduceTime);
        // 创建一个FKPrinter对象, 当成FKOutput使用
        id<FKOutput> out = [[FKPrinter alloc] init];
        // 调用FKOutput协议中的方法
        [out addData: @"孙悟空"];
        [out addData: @"猪八戒"];
        [out output]; 
    }
    return 0;
}

上面程序中创建了一个 FKPrinter 对象,该 FKPrinter 对象包含上面3个协议的方法,因此可以调用3个协议中的方法。

程序中的两行粗体字代码没有使用FKPrinter来定义变量,而是使用协议来定义变量,那么这些变量只能调用该协议中声明的方法,否则编译器会提示错误。如果程序需要使用协议来定义变量,则有如下两种语法。

  1. NSObject<协议 1,协议 2...>*变量;

  2. d<协议 1.协议 2...>变量;

通过上面的语法格式定义的变量,它们的编译时类型仅仅是所遵守的协议类型,因此只能调用该协议中定义的方法。

对比正式协议与非正式协议,不难发现存在如下差异。

  1. 非正式协议通过为NSObject创建类别来实现;而正式协议则直接使用@protocol创建。遵守非正式协议通过继承带特定类别的NSObiect来实现:而遵守正式协议则有专门的Objective-C 语法。
  2. 遵守非正式协议不要求实现协议中定义的所有方法:而遵守正式协议则必须实现协议中定义的所有方法。

为了弥补遵守正式协议必须实现协议的所有方法造成的灵活性不足,Objective-C2.0新增**@optional、@required** 两个关键字,其作用如下。

  1. @optional:位于该关键字之后、@optional 或@end 之前声明的方法是可选的--实现类既可选择实现这些方法,也可不实现这些方法。
  2. @required:位于该关键字之后、@required 或@end 之前声明的方法是必需的--实现类必须实现这些方法。如果没有实现这些方法,编译器就会提示警告。@required 是默认行为。

例如以下协议:

objc 复制代码
#import<Foundation/Foundation.h>
@protocol FKOutput
@optional
- (void) output;
@required
- (void) addData: (NSString*) msg;
@end

上面协议中定义了两个方法, 该协议的实现类可选实现方法 output 方法, 但必须实现addData: 方法, 否则编译器就会提示警告

通过在正式协议中使用@optional, @required 关键字, 正式协议完全可以代替非正式协议的功能

委托 (Delegate)

委托 (Delegate) 是一种设计模式, 让一个对象把某些任务委托给另一个对象去处理

基本结构

委托方(Delegator):主动方。负责定义协议,并持有一个遵守该协议的代理对象。

协议(Protocol):约定书。规定了代理方需要实现的方法(哪些是必须的,哪些是可选的)。

代理方(Delegate):被动方。遵守协议并实现其中的方法,用来响应委托方的请求。

##委托实现

首先定义协议, (规定代理需要做哪些事情)

objc 复制代码
OrderDelegate.h 

#import<Foundation/Foundation.h>
@protocol OrderDelegate <NSObject> 
- (void) didReceiveOrder: (NSString*) food; 
@end

委托方 (持有代理的引用)

接口部分

objc 复制代码
Boss.h  
    
#import "OrderedDelegate.h"
@interface Boss: NSObject <OrderDelegate>
@property (nonatomic, weak) id<OrderDelegate> delegate;  
- (void) placeOrder; 
@end 

注意: 这里是weak, 避免 Boss 和 Secretary 互相强引用导致内存泄漏

实现部分

objc 复制代码
Boss.m
    
#import "Boss.h" 
@implementation Boss 
- (void) placeOrder {
    if ([self.delegate respondsToSelector: @selectot(didReceiverOrder:)]) {
        [self.delegate didReceiverOrder: @"hanbao"]; 
    }
} 
@end

respondsToSelector:是 Objective-C 中NSObject协议的核心方法,结合@selector(didReceiveOrder:)使用时,作用是检查代理对象是否实现了didReceiveOrder:这个协议方法,避免直接调用未实现的方法导致程序崩溃。

核心功能

接收一个SEL (方法选择器)参数,返回BOOL值:

  • YES:代理对象实现了该方法;

  • NO:代理对象未实现该方法。

  • 在调用代理方法前执行此检查,是 Objective-C 代理模式的必做安全校验

代理方 (实现协方法)

接口部分

objc 复制代码
Secretary.h
    
#import"OrderDelegate.h"
@interface Secretary: NSObject <OrderDelegate> 
@end

实现部分

objc 复制代码
Secretary.m 
  
#import"Secretary.h"  
@implementation Secretary
- (void) didReceiveOrder: (NSString*) foog {
    NSLog(@"接到订单, 去订 %@", food); 
}
@end

测试

objc 复制代码
main.m

#import"Boss.h"
#import"Secretary.h" 
int main(int argc, char* argv[]) {
    @autoreleasepool {
        Boss* boss = [[Boss alloc] init]; 
        Secretary* secretary = [[Secretary alloc] init]; 
        
        boss.delegate = secretary; 	// 设置代理
        [boss placeOrder]; 		// 触发代理方法
    }
    return 0; 
}
相关推荐
for_ever_love__2 小时前
Objective-C学习 NSDictionary,NSMutableDictionary 功能详解
开发语言·学习·ios·objective-c
鹅天帝2 小时前
20230319网安学习日志——XSS漏洞
前端·学习·web安全·网络安全·xss
lars_lhuan2 小时前
Go Once
开发语言·golang
hongtianzai2 小时前
Go vs Java:终极性能对决
java·开发语言·golang
汤姆yu2 小时前
基于python大数据的天气可视化及预测系统
大数据·开发语言·python
转角羊儿2 小时前
精灵图案例
开发语言·前端·javascript
l1t2 小时前
Qwen 3.5plus编写的求解欧拉计划901题python程序优化
开发语言·python
T0uken2 小时前
【Python】docxnote:优雅的 Word 批注
开发语言·python·word
9稳2 小时前
基于智能巡检机器人与PLC系统联动控制设计
开发语言·网络·数据库·嵌入式硬件·plc