iOS 准备学习iOS基础·OC高级篇iOS 深度好文

iOS 之 Protocol 详解

2016-09-27  本文已影响5879人  雨雪传奇

iOS开发中,Protocol是一种经常用到的设计模式,苹果的系统框架中也普遍用到了这种方式,比如UITableView中的<UITableViewDelegate>,以及<NSCopying><NSObject>这样的协议。我想大家也都自定义过协议,一般都用于回调,或者数据传递。不过,用久了以后,不知道大家是否会有一些困惑:协议只能用于委托代理吗?为什么系统中定义了这么多协议?为什么感觉分类和协议好像?系统中又为何会定义这么多分类?被这些问题轰炸后,我不禁又开始想,协议到底是个啥玩意?

带着这样的目地,我阅读了一些文档书籍,最后整理了以下的内容:


1. protocol 是什么?

iOS 开发文档是这样定义的:(翻译略渣,请见谅)

A protocol declares a programmatic interface that any class may choose to implement. Protocols make it possible for two classes distantly related by inheritance to communicate with each other to accomplish a certain goal. They thus offer an alternative to subclassing. Any class that can provide behavior useful to other classes may declare a programmatic interface for vending that behavior anonymously. Any other class may choose to adopt the protocol and implement one or more of its methods, thereby making use of the behavior. The class that declares a protocol is expected to call the methods in the protocol if they are implemented by the protocol adopter.

协议声明了任何类都能够选择实现的程序接口。协议能够使两个不同继承树上的类相互交流并完成特定的目的,因此它提供了除继承外的另一种选择。任何能够为其他类提供有用行为的类都能够声明接口来匿名的传达这个行为。任何其他类都能够选择遵守这个协议并实现其中的一个或多个方法,从而利用这个行为。如果协议遵守者实现了协议中的方法,那么声明协议的类就能够通过遵守者调用协议中的方法。

2. protocol 里有些啥?

协议中能够声明方法,以及属性。然后问题就来了,不是不能定义成员变量的吗?

对,的确不能定义成员变量,但是属性是什么?属性包含了三个东西:成员变量、setter方法、getter方法。在类中定义的属性,当然三者都有,然而协议中定义的属性只有获取和设置方法,没有成员变量,这就要求该协议的遵守者必须自己写出settergetter方法的实现。但是有一种情况是不需要的,那就是遵守者本来就有这个属性,此时系统会为这个属性自动生成设置获取方法,既然已经实现了,那么遵守者就没必要去实现协议中的这个属性了。

尽管可以实现“伪属性”,但是,我们还是应该尽量把属性定义在主接口中,而不应该定义在协议中

还有一点,也是很重要的一点,为什么自定义的协议后面会有这么一个东西<NSObject>?

协议也能继承。既可以继承自自定义的协议,也可以继承自系统的协议。
我们在定义协议的时候,一般都是直接继承自<NSObject>,为什么系统要默认让协议继承自这个协议呢?

因为这个协议中定义了一些基本的方法,由于我们使用的所有类都继承NSObject这个基类,而这个基类遵守了<NSObject>这个协议,那么也就实现了其中的那些方法,这些方法当然可以由NSObject及其子类对象调用,但是在不知道遵守者类型的时候需要用到id <协议名>这样的指针,这个指针在编译期并不知道自己指向哪个对象,唯一能调用的便是协议中的方法,然而有时候又需要用一些基本的方法,比如要辨别id <协议名>这个指针所指的对象属于哪个类,就要用到-isMemberOf:这个方法,而这个方法是<NSObject>这个协议中的方法之一,所以,我们自定义的协议都需要继承<NSObject>。本段一开始便说道:<NSObject>中的方法在NSObject基类中实现了,那么无需再关心实现了,直接调用<NSObject>中的方法吧。

3. protocol 可以写在哪?

写在头文件中,写在实现文件的类扩展中。

前者:可以当做是给这个类添加了一些外部接口。
后者:可以当做是给这个类添加了一些私有接口。

不过,如果子类自身又遵循了这个协议,但并没有实现,那么在运行时,系统会一级级往上查找,直到找到父类的方法实现。也就是说,只要知道苹果的私有方法名,并且确保自己的类是这个私有方法所属类的子类,就可以在子类中通过只声明不实现的方式执行父类中该私有方法的实现。

4. protocol 里的方法由谁实现,由谁调用

实现:遵守协议者及其子类
调用:遵守协议者、其子类、id <协议名>

5. protocol 的分类:正式协议 和 非正式协议(类别)

iOS 文档是这样定义的:

There are two varieties of protocol, formal and informal:

A formal protocol declares a list of methods that client classes are expected to implement. Formal protocols have their own declaration, adoption, and type-checking syntax. You can designate methods whose implementation is required or optional with the @required and @optional keywords. Subclasses inherit formal protocols adopted by their ancestors. A formal protocol can also adopt other protocols. Formal protocols are an extension to the Objective-C language.

An informal protocol is a category on NSObject, which implicitly makes almost all objects adopters of the protocol. (A category is a language feature that enables you to add methods to a class without subclassing it.) Implementation of the methods in an informal protocol is optional. Before invoking a method, the calling object checks to see whether the target object implements it. Until optional protocol methods were introduced in Objective-C 2.0, informal protocols were essential to the way Foundation and AppKit classes implemented delegation.

类目实际上是一种特殊的协议。我们没法通过正式协议为系统的类添加方法,因为我们无法编辑系统的类。当然,我们也可以选择继承的方式,但是,这就会创建一个新的类,并不是特别划算。所以,类目这种方式派上用场了。

但是为什么系统框架中使用了这么多的类目呢?设计者当初为什么不把类目中的方法写到主接口中?

原因在于要将众多的方法打散到各处,要将同一类型功能的接口都封装在一个类目中。这样做能够减少主接口中的代码量,也便于调试。我们还可以把类中的私有方法全部封装在名为Private的类目中,然后在实现文件中引入这个类目,这样做可以把共有的方法定义在一个类目中,也很容易能够清楚哪些是私有方法。

协议,尤其是类目,一定要给类目的名称和方法名添加自己的特有前缀,这样做既不会和系统的类目冲突,也不会在将自己的代码开源后和其他使用者的类目冲突。

6. protocol 有哪些作用,用在哪些地方

总结

协议就是定义公共接口的地方,只要遵守协议,就等于在头文件中定义了这些方法,只要实现就行了。之所以有这样的设计,是因为要将共同的行为抽象出来:不同的类有不同的作用和特征,这也是面向对象的特点,但是即使千差万别,还是会有某些相似点的,这些相似的地方就可以抽象出来做成协议。但有时候这些共同的部分并不是本身就有的,而是人为的添加的,我们要求这些类具有共同的部分,而不管这些类是多么千差万别。有人会问,为什么不写一个公共的父类呢?子类继承父类,这样就能共有某些方法了?

当然是有这样的设计的,Foundation框架下类的设计就是这样一层一层写下去的,最具相同性的属性和方法声明的位置绝对更靠前。但是,如果同时需要遵守协议的是来自两个不同继承树上的类呢?难道是找到它们共有的祖先类,然后把方法写在那里面吗?显然这么做是不行的,因为这样会导致两个继承树下的所有子类都可以调用,然而并不是所有子类都需要这些方法,所以还是得要用协议。

很多人会看到有一个<NSObject>的协议,这个协议和NSObject这个类同名,由NSObject遵守,为什么不把这些方法直接写到NSObject类中呢?因为cocoa框架中的基类不止NSObject一个,还有NSProxy这样的类存在,那么<NSObject>这个协议就很容易明白了,它抽象出了所有基类都需要的方法,为基类提供共有方法。还有一个原因的话,在上文中已经说明了,自定义的类要继承自这个协议,以供匿名对象id<协议名>使用。

上一篇下一篇

猜你喜欢

热点阅读