美文网首页
iOS----Protocols

iOS----Protocols

作者: 彬至睢阳 | 来源:发表于2019-01-05 15:36 被阅读0次

     协议

         协议声明可以由任何类实现的方法。协议至少在三种情况下是有用的:

         1.要声明其他人期望实现的方法

         2.在隐藏对象的类时声明对象的接口

         3.捕获不具有层次关系的类之间的相似性

         为其他人实现声明接口

         类和类别接口声明与特定类关联的方法,主要是类实现的方法。另一方面,非正式和正式协议声明独立于任何特定类的方法,但是任何类,也许还有许多类,都可能实现这些方法。

         协议只是一个方法声明的列表,没有附加到类定义上。例如,这些报告鼠标上用户操作的方法可以收集到一个协议中:

         - (void)mouseDown:(NSEvent *)theEvent;

         - (void)mouseDragged:(NSEvent *)theEvent;

         - (void)mouseUp:(NSEvent *)theEvent;

         任何想要响应鼠标事件的类都可以采用该协议并实现其方法。

         协议将方法声明从对类层次结构的依赖中解放出来,因此它们可以以类和类别所不能的方式使用。协议列出在某处实现的(或可能实现的)方法,但实现它们的类的标识不重要。值得关注的是一个特定的类是否符合协议——它是否具有协议声明的方法的实现。因此,对象不仅可以根据从同一类继承而来的相似性来分组,而且还可以根据它们在符合同一协议方面的相似性来分组。继承层次结构中不相关分支中的类可能具有相同的类型,因为它们符合相同的协议。

         协议可以在面向对象的设计中发挥重要作用,特别是当项目被许多实现者分割或合并在其他项目中开发的对象时。Cocoa软件大量使用协议来支持通过Objective-C消息的进程间通信。

         然而,Objective-C程序不需要使用协议。与类定义和消息表达式不同,它们是可选的。一些Cocoa框架使用它们;一些不喜欢。这完全取决于手头的任务。

         方法,以便其他人实现

         如果您知道对象的类,您可以查看它的接口声明(以及它继承的类的接口声明),以找到它响应的消息。这些声明声明它可以接收到的消息。协议为它提供了一种方式,也可以为它发送的消息做广告。

         沟通是双向的;对象发送消息和接收消息。例如,一个对象可能将某个操作的责任委托给另一个对象,或者有时候它可能只需要向另一个对象请求信息。在某些情况下,对象可能愿意将其操作通知其他对象,以便他们可以采取可能需要的任何附带措施。

         如果您将发送方的类和接收方的类作为同一个项目的一部分进行开发(或者如果其他人向您提供了接收方及其接口文件),那么这种通信很容易协调。发送方只需导入接收方的接口文件。导入的文件声明了发送方在其发送的消息中使用的方法选择器。

         但是,如果您开发了一个对象,该对象将消息发送给尚未定义的对象(即您将留给其他人实现的对象),那么您将没有接收方的接口文件。您需要另一种方法来声明在消息中使用但不实现的方法。协议就是为了这个目的而制定的。它通知编译器类使用的方法,也通知其他实现程序它们需要定义的方法,以便它们的对象与您的对象一起工作。

         例如,假设您开发了一个对象,通过发送helpOut:和其他消息请求另一个对象的帮助。您可以提供一个辅助实例变量来记录这些消息的出口,并定义一个辅助方法来设置实例变量。该方法允许其他对象注册为对象消息的潜在接收者:

         - setAssistant:anObject

         {

         assistant = anObject;

         }

         然后,无论何时要向助手发送消息,都要进行检查,以确保接收者实现能够响应的方法:

         - (BOOL)doWork

         {

         ...

         if ( [assistant respondsToSelector:@selector(helpOut:)] ) {

         [assistant helpOut:self];

         return YES;

         }

         return NO;

         }

         因为,在编写这段代码时,您不知道什么类型的对象可能注册为助手,您只能声明helpOut:方法的协议;您不能导入实现它的类的接口文件。

         为匿名对象声明接口

         协议可用于声明匿名对象(未知类的对象)的方法。匿名对象可以表示服务或处理一组有限的函数,特别是在只需要一个同类对象的情况下。(在定义应用程序体系结构中扮演基本角色的对象,以及在使用之前必须初始化的对象,都不适合匿名。)

         对象对开发人员来说当然不是匿名的,但是当开发人员向其他人提供对象时,对象是匿名的。例如,考虑以下情况:

         1.为其他人提供框架或对象套件以供使用的人可以包含没有通过类名或接口文件标识的对象。由于没有名称和类接口,用户无法创建类的实例。相反,供应商必须提供一个现成的实例。通常,另一个类中的方法返回一个可用对象:id formatter = [receiver formattingService];

         该方法返回的对象是一个没有类标识的对象,至少供应商不愿意透露该类标识。要使其具有任何用途,供应商必须愿意识别至少一些它可以响应的消息。通过将对象与协议中声明的方法列表相关联来标识消息。

        2. 您可以向其他应用程序中的远程对象——对象——发送Objective-C消息。

         每个应用程序都有自己的结构、类和内部逻辑。但是您不需要知道另一个应用程序是如何工作的,或者它的组件是什么来与它通信。作为一个局外人,您所需要知道的只是您可以发送什么消息(协议)以及将它们发送到哪里(接收方)。

         将其对象之一作为远程消息的潜在接收者发布的应用程序还必须发布一个协议,声明对象将用于响应这些消息的方法。它不需要揭示任何关于这个物体的东西。发送应用程序不需要知道对象的类,也不需要在自己的设计中使用这个类。它所需要的只是协议。

         协议使匿名对象成为可能。如果没有协议,就无法在不标识对象类的情况下声明对象的接口。

         注意:即使匿名对象的提供者不显示它的类,对象本身在运行时也会显示它。类消息返回匿名对象的类。然而,发现这些额外的信息通常没有什么意义;协议中的信息就足够了。

         正式的协议

         Objective-C语言提供了一种将方法列表(包括声明的属性)正式声明为协议的方式。语言和运行时系统支持正式协议。例如,编译器可以基于协议检查类型,对象可以在运行时自省,以报告它们是否符合协议。

         声明一个协议

         使用@protocol指令声明正式协议:

         @protocol 协议的名字

         方法的声明

         @end

         例如,可以这样声明XML表示协议

         @protocol MyXMLSupport

         - initFromXMLRepresentation:(NSXMLElement *)XMLElement;

         - (NSXMLElement *)XMLRepresentation;

         @end

         与类名不同,协议名没有全局可见性。它们生活在自己的名称空间中。

         可选的协议方法

         协议方法可以使用@optional关键字标记为可选。对应于@optional modal关键字,有一个@required关键字来正式表示默认行为的语义。您可以使用@optional和@required将协议划分为您认为合适的部分。如果您没有指定任何关键字,默认值是@required。

         @protocol MyProtocol

         - (void)requiredMethod;

         @optional

         - (void)anOptionalMethod;

         - (void)anotherOptionalMethod;

         @required

         - (void)anotherRequiredMethod;

         @end

         非正式协议

         除了正式协议之外,您还可以通过将方法分组到类别声明中来定义非正式协议:

         @interface NSObject ( MyXMLSupport )

         - initFromXMLRepresentation:(NSXMLElement *)XMLElement;

         - (NSXMLElement *)XMLRepresentation;

         @end

         非正式协议通常声明为NSObject类的类别,因为它广泛地将方法名称与从NSObject继承的任何类关联起来。因为所有类都从根类继承,所以方法并不局限于继承层次结构的任何部分。(也可以将非正式协议声明为另一个类的类别,以将其限制在继承层次结构的某个分支上,但这样做的理由很少。)

         当用于声明协议时,类别接口没有相应的实现。相反,实现协议的类在它们自己的接口文件中再次声明这些方法,并在它们的实现文件中与其他方法一起定义它们。

         非正式协议将类别声明的规则转换为列出一组方法,但不将它们与任何特定的类或实现关联。

         由于是非正式的,分类中声明的协议没有得到太多的语言支持。编译时没有类型检查,运行时也没有检查对象是否符合协议。为了获得这些好处,您必须使用正式的协议。当所有方法都是可选的(比如委托)时,非正式协议可能会很有用,但是(在OS X v10.5和更高版本中)通常使用正式协议和可选方法更好。

         协议的对象

         正如类在运行时由类对象表示,方法由选择器代码表示一样,正式协议也由协议类的特殊数据类型实例表示。处理协议(在类型规范中使用协议除外)的源代码必须引用相应的协议对象。

         在许多方面,协议与类定义相似。它们都声明方法,并且在运行时它们都由对象表示——类由类的实例表示,协议由协议的实例表示。与类对象一样,协议对象是由源代码中找到的定义和声明自动创建的,并由运行时系统使用。它们没有在程序源代码中分配和初始化。

         源代码可以使用@protocol()定向引用协议对象——与声明协议的指令相同,只不过这里有一组后括号。括号内是协议名称:

         Protocol *myXMLSupportProtocol = @protocol(MyXMLSupport);

         这是源代码调用协议对象的惟一方法。与类名不同,协议名不指定对象,除了@protocol()内部。

         编译器为它遇到的每个协议声明创建一个协议对象,但前提是该协议也是:

         1.被一个类采用的,或

         2.在源代码中引用(使用@protocol())

         声明但未使用的协议(除了下面描述的类型检查之外)在运行时不会由协议对象表示

         采用一个协议

         采用协议在某些方面类似于声明超类。两者都为类分配方法。超类声明指定它继承的方法;协议分配在协议列表中声明的it方法。如果一个类在其声明中在超类名称后的尖括号中列出了该协议,则称其采用正式协议:

         @interface ClassName : ItsSuperclass < protocol list >

         类别采用协议的方式大致相同:

         @interface ClassName ( CategoryName ) < protocol list >

         一个类可以采用多个协议;协议列表中的名称用逗号分隔。

         @interface Formatter : NSObject < Formatting, Prettifying >

         采用协议的类或类别必须实现协议声明的所有所需方法,否则编译器将发出警告。上面的Formatter类将定义它所采用的两个协议中声明的所有必需方法,以及它本身可能声明的任何方法。

         采用协议的类或类别必须导入声明协议的头文件。在采用的协议中声明的方法不会在类或类别接口的其他地方声明。

         类可以简单地采用协议而不声明其他方法。例如,下面的类声明采用格式化和美化协议,但没有声明自己的实例变量或方法:

         @interface Formatter : NSObject < Formatting, Prettifying >

         @end

         符合协议

         如果一个类采用了该协议,或者从另一个采用该协议的类继承了该协议,那么它就符合正式协议。一个类的实例据说符合它的类所遵循的同一组协议。

         因为类必须实现它所采用的协议中声明的所有必需的方法,所以说类或实例符合协议就等于说它的指令库中有协议声明的所有方法。

         通过发送conformsToProtocol:消息,可以检查对象是否符合协议。

         if ( ! [receiver conformsToProtocol:@protocol(MyXMLSupport)]  ) {

         // 对象不符合MyXMLSupport协议

         // 如果您希望接收方实现MyXMLSupport协议中声明的方法,那么这可能是一个错误

         }

         (注意,还有一个类方法的名称与conformstoprotocol:相同。)

         conformsToProtocol: test与respondsToSelector: test类似,只是测试是否采用了协议(以及它声明实现的所有方法),而不只是测试是否实现了一个特定的方法。因为它检查协议中的所有方法,所以conformsToProtocol:可能比respondsToSelector:更有效。

         conformsToProtocol: test也类似于isKindOfClass: test,只是它测试的是基于协议的类型,而不是基于继承层次结构的类型。

         协议在协议

         一个协议可以使用与类采用协议相同的语法合并其他协议:

         @protocol ProtocolName < protocol list >

         尖括号之间列出的所有协议都被认为是协议名称协议的一部分。例如,如果分页协议合并了格式化协议

         @protocol Paging < Formatting >

        任何符合分页协议的对象也符合格式。类型声明,例如

         id someObject;

         和conformsToProtocol:消息如

         if ( [anotherObject conformsToProtocol:@protocol(Paging)] )

         ...

         还需要只提到分页协议来测试格式的一致性。

         当类采用协议时,它必须实现协议声明的所需方法,如前所述。此外,它必须符合所采用的协议合并的任何协议。如果合并的协议还合并了其他协议,那么类也必须符合这些协议。类可以使用以下任何一种技术来遵守合并的协议:

         实现协议声明的方法

         从采用协议并实现方法的类继承

         例如,假设寻呼机类采用分页协议。如果寻呼机是NSObject的子类,如下所示:

         @interface Pager : NSObject < Paging >

         参考其他协议

         在处理复杂应用程序时,您偶尔会发现自己编写的代码如下:、

         #import "B.h"

         @protocol A

         - foo:(id )anObject;

         @end

         协议B是这样声明的:

         #import "A.h"

         @protocol B

         - bar:(id )anObject;

         @end

         在这种情况下,循环性会导致两个文件都不能正确编译。要打破这种递归循环,必须使用@protocol指令转发引用所需的协议,而不是导入定义协议的接口文件:

         @protocol B;

         @protocol A

         - foo:(id )anObject;

         @end

    注意,以这种方式使用@protocol指令只是通知编译器B是稍后定义的协议。它不导入定义协议B的接口文件。

    相关文章

      网友评论

          本文标题:iOS----Protocols

          本文链接:https://www.haomeiwen.com/subject/knazlqtx.html