【iOS】—— 协议(protocol)和委托_西邮郭富城的博客-程序员宅基地

技术标签: objective-c  iOS笔记  

协议的作用:

类似于接口,用于定义多个类应该遵守的规范,但不会为这些方法提供实现,方法的实现则交给类去完成。

规范、类和实例的抽象关系

sd

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

协议不提供任何实现。协议体现的是规范和实现分离的设计哲学。让规范和实现分离也正是协议的好处,是一种松耦合的设计。

使用类别实现非正式协议

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

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

例如:

以NSObject为基础,定义一个类别,类别名称为Eatable,代码如下
#import <Foundation/Foundation.h>

//以NSObject为基础定义Eatable类别
@interface NSObject (Eatable)
- (void) taste;
@end

上面为NSObject的Eatable类别中定义了一个taste:方法,接下来所有继承NSObject类的子类都会自动带有该方法,而且NSObject的子类可以根据需要,自行决定是否要实现该方法。

Eatable类别作为一个非正式协议使用,那么相当于定义了一个规范,因此,遵守该协议的子类通常都会实现这个方法。

再为NSObject(Eatable)派生一个子类:
#import <Foundation/Foundation.h>
#import "NSObject+Eatable.h"

//定义类的接口部分
@interface FKApple : NSObject
@end
#import "FKApple.h"

//为FKApple提供实现部分
@implementation FKApple
- (void) taste {
    
    NSLog(@"苹果营养丰富,口味很好!");
}
@end

从上述的程序可以看到,虽然接口部分没有定义任何方法,但是这丝毫不会影响到该类的功能,因为它继承了NSObject(Eatable),只要在FKApple类的实现部分实现taste方法即可。

下面是FKApple类的测试代码:
#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc, char* argv[]) {
    
    @autoreleasepool {
    
        FKApple* app = [[FKApple alloc] init];
        [app taste];
    }
    return 0;
}

输出结果为:
21

Objective-C并不强制实现该协议中的所有方法,但是若不实现FKApple类中的taste方法,而且非正式协议本身也没有实现该方法,就会引发下面的错误:
34

正式协议的定义

正式协议的基本语法如下:
@protocol 协议名 <父协议1, 父协议2>
{
    
   零个到多个方法定义...
}

上面语法的详细说明如下:

  • 协议名与类名采用相同的规则命名。
  • 一个协议可以有多个直接父协议,但协议只能继承协议,不能继承类。
  • 协议中定义的方法只有方法签名,没有方法实现;协议中包含的方法既可是类方法,也可是实例方法。

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

下面定义一个协议:
#import <Foundation/Foundation.h>

//定义协议
@protocol Output
//定义协议的方法
- (void) output;
- (void) addData: (NSString*) msg;
@end

上面定义了一个Output协议,其中定义了两个方法,而协议并不关心方法的实现,就相当于定义了一个接口。

再定义一个Productable协议,该协议代表了所有的产品都需要遵守的规范:
#import <Foundation/Foundation.h>

//定义协议
@protocol Productable
//定义协议的方法
- (NSDate*) getProduceTime;
@end
接下来定义一个打印机协议,该协议同时继承上面两个协议:
#import <Foundation/Foundation.h>
#import "Output.h"
#import "Productable.h"

//定义协议,继承了上面两个协议
@protocol Printable <Output, Productable>
//定义协议的方法
- (NSString*) PrintColor;
@end

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

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

遵守(实现)协议

在类定义的接口部分可指定该类继承的父类,以及遵守的协议,语法如下:
@interface 类名: 父类 <协议1, 协议2...>

从上面的语法格式可以看出,一个类可以同时遵守多个协议。

为Printable协议提供一个实现类:Printer,该实现类的接口部分代码如下:
#import <Foundation/Foundation.h>
#import "Printable.h"

//定义类的接口部分,继承NSObject,遵守Printable协议
@interface Printer :  NSObject <Printable>
@end
Printer类实现部分代码如下:
#import "Printer.h"
#import "Printable.h"
#define MAX_CACHE_LINE 10

@implementation Printer
{
    
    NSString* printData[MAX_CACHE_LINE];
    int dataNum;
}
- (NSString*) PrintColor {
    
    return @"红色";
}
- (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];
}
@end

Printer类实现了Printable协议,且也实现了Printable和Printable的两个父协议中的所有方法。假如实现类没有实现协议中的PrintColor方法,编译器会提示如下警告。

342

测试程序的代码如下:
#import <Foundation/Foundation.h>
#import "Printer.h"

int main(int argc, char* argv[]) {
    
    @autoreleasepool {
    
        //创建Printer对象
        Printer* printer = [[Printer alloc] init];
        //调用Printer对象的方法
        [printer addData:@"疯狂iOS讲义"];
        [printer addData:@"疯狂XML讲义"];
        [printer output];
        [printer addData:@"疯狂Android讲义"];
        [printer addData:@"疯狂Ajax讲义"];
        [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];
    }
    return 0;
}

输出结果如下:
323

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

如果程序需要使用协议来定义变量,有如下两种方法:
  • NSObject<协议1, 协议2…>* 变量;
  • id<协议1, 协议2…> 变量;
    通过上面的语法格式定义的变量,它们的编译时类型仅仅只是所遵守的协议类型,因此只能调用该协议中定义的方法。
正式协议与非正式协议的差异:
  • 非正式协议通过为NSObject创建类别来实现,而正式协议则直接使用@protocol创建。
  • 遵守非正式协议通过继承带特定类别的NSObject来实现;而遵守正式协议则有专门的Objective-C语法。
  • 遵守非正式协议不要求实现协议中定义的所有方法;而遵守正式协议则必须实现协议中定义的所有方法。
为了弥补遵守正式协议必须实现协议的所有方法造成灵活性不足,OC新增了@optional、@required两个关键字,其作用如下:
  • @optional:位于该关键字之后、@required或@end之前声明的方法时可选的——实现类既可以选择实现这些方法,也可以不实现这些方法。
  • @required:位于该关键字之后、@optional或@end之前声明的方法是必需的——实现类必须实现这些方法。如果没有实现这些方法,编译器就会提示警告。@required是默认行为。

例如定义如下协议:

@protocol Output
//定义协议的方法
@optional
- (void) output;
@required
- (void) addData: (NSString*) msg;
@end

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

协议与委托(delegate)

委托的定义:

就是某个对象指定另一个对象处理某些特定任务的设计模式。通俗来说,就是“某个对象”把要做的事情委托给“另一个对象”去做。

其中“某个对象”被称作委托者,“另一个对象”被称作是被委托者,即代理。

委托和代理的关系

如下图,委托方通过某种方式把任务分派出去给代理方处理,而两者之间的联系便是协议。

324

在程序中:一般情况下

委托需要做的工作有:
  • 定义协议与方法
  • 声明委托变量
  • 设置代理
  • 通过委托变量调用委托方法
代理需要做的工作有:
  • 遵循协议
  • 实现委托方法
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_55124878/article/details/117910699

智能推荐

高通 AR SDK对模型进行控制(平移、旋转、缩放)_arcamera中如何实现模型的移动-程序员宅基地

1、 写本地方法[java] view plaincopy// 设置缩放尺寸、旋转角度 privatenative void setScale(float scale); private nativevoid setAngle(float angle); 2、 使用javah命令生成头文件。_arcamera中如何实现模型的移动

n & (n - 1)的用法-程序员宅基地

2的幂: 1 1 &amp; 0=0 2 10 &amp; 01=0 4 100 &amp; 011=0 8 1000 &amp; 0111=0 16 10000 &amp; 01111=0#include &lt;iostream&gt;#include &lt;cstdio&gt;using namespace std;bool fun(int v){ bool flag =

SAS数据导出导入_sas结果集导出到excel文件怎样控制不让列名输出-程序员宅基地

http://blog.163.com/chensx_326/blog/static/1540079520078237414664/一、导入导出文本文件(txt格式)、纯数据文件(dat格式);其实都是导入导出DLM文件(*.*),需要指定分隔符号。如下(以txt为例,dat同样):1.TAB分割,第一行为变量名PROC IMPORT OUT=_sas结果集导出到excel文件怎样控制不让列名输出

Python 爬虫之网络请求-程序员宅基地

urllib库中常用函数的使用#!/usr/bin/python3# -*- coding:utf-8 -*-# @Time : 2018-11-10 21:25# @Author : Manu# @Site : # @File : urllib_lib.py# @Software: PyCharmfrom urllib import requestfrom...

HDMI over ip 延长芯片方案_qcw5001-程序员宅基地

应用:家庭影院,安防监控,视频会议,户外荧屏,电视广播;1. HDMI1.3 over IP(网线)规格芯片组合;潜创微科技的QCW5001/QCW5002,规格:HDMI输入:1080P60,HDMI输出:1080P60;距离:200米;特色:支持红外远程遥控;优势:H.265画质好,低延时;标准的TCP/IP协议;KVM远程传输,一对多,多对一,多对多分布式矩阵,智能化网页参数设置;带宽小,兼容性好;2. HDMI1.3 over IP(无线)规格:潜创微科技的HDMI输入:1080P60;HD_qcw5001

c++ 数据结构 用递归方法解决迷宫问题-程序员宅基地

c++,数据结构——用递归方法解决迷宫问题

随便推点

软件架构师考试备战图谱-程序员宅基地

在备战2020年的系统架构师考试过程中,发现了很多知识盲点,边学习边总结知识内容如下脑图,希望自己能够顺利通关,脑图会持续更新_软件架构师考试

1.Ubuntu下使用git的基本步骤之版本的创建与回退_ubuntu git 回退版本-程序员宅基地

安装gitsudo apt install git使用1.版本创建1.git add your_file2.git commit -m 'version1'3.更新版本,再次执行以上步骤2.版本回退1.可用git log,查看版本信息。2.若想回退到version1,git reset --hard HEAD^#后面的^是前几个版本就写几个或者git reset -..._ubuntu git 回退版本

java8性能_Java 8 Stream的性能到底如何?_我是铁匠的博客-程序员宅基地

Java 8提供的流的基于Lambda表达式的函数式的操作写法让人感觉很爽,笔者也一直用的很开心,直到看到了Java8 Lambda表达式和流操作如何让你的代码变慢5倍,笔者当时是震惊的,我读书少,你不要骗我。瞬间我似乎为我的Server Application速度慢找到了一个很好地锅,不过这个跟书上讲的不一样啊。于是笔者追本溯源,最后找到了始作俑者自己的分析:原文不久之前我在社区内发表了这篇文章..._java8 stream性能

Android内核学习笔记-程序员宅基地

一、init二、binder《ANDROID框架揭秘》第六章 - 第十章 以生动的形式讲述了binder,但是不够详细。值得参考。《Android系统源代码情景分析》第五章 描叙详细,但是不够生动。值得参考。

卷积神经网络(CNN)的原理_cnn如何找到拟合函数-程序员宅基地

卷积神经网络(CNN)的原理本文主要内容:CNN的定义CNN的构成CNN的使用CNN的代码(Tensorflow)CNN的定义:CNN是一种前馈神经网络,前馈神经网络是一种最简单的神经网络。每个神经元只与前一层的神经元相连。接收前一层的输出,并输出给下一层.各层间没有反馈。 简单来说,是一种单向多层结构。同一层神经元之间没有互相连接,层间信息传达只沿一个方向进行。除..._cnn如何找到拟合函数

菜单 java,java菜单的实现_书香拌饭的博客-程序员宅基地

java 实现动态菜单,,java菜单的实现,java菜单权限实现JAVA语言实现下来菜单源程序_计算机软件及应用_IT/计算机_专业资料 暂无评价|0人阅读|0次下载|举报文档JAVA语言实现下来菜单源程序_计算机软件及应用_IT/计算机_专业......java菜单_计算机软件及应用_IT/计算机_专业资料。程序中实现菜单需要用到 ...1.菜单(菜单条组件 MenuBar、菜单组件 Menu..._java中创建菜单类menu 编写方法实现各功能 showloginmenu()方法,实现显示登录菜单