iOS 之 Protocol 详解
在iOS
开发中,Protocol
是一种经常用到的设计模式,苹果的系统框架中也普遍用到了这种方式,比如UITableView
中的<UITableViewDelegate>
,以及<NSCopying>
、<NSObject>
这样的协议。我想大家也都自定义过协议,一般都用于回调,或者数据传递。不过,用久了以后,不知道大家是否会有一些困惑:协议只能用于委托代理吗?为什么系统中定义了这么多协议?为什么感觉分类和协议好像?系统中又为何会定义这么多分类?被这些问题轰炸后,我不禁又开始想,协议到底是个啥玩意?
带着这样的目地,我阅读了一些文档书籍,最后整理了以下的内容:
-
protocol
是什么? -
protocol
里有些啥? -
protocol
可以写在哪? -
protocol
里的方法由谁实现,由谁调用? -
protocol
分类:正式协议和非正式协议? -
protocol
有哪些作用,能用在哪些地方?
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
方法。在类中定义的属性,当然三者都有,然而协议中定义的属性只有获取和设置方法,没有成员变量,这就要求该协议的遵守者必须自己写出setter
和getter
方法的实现。但是有一种情况是不需要的,那就是遵守者本来就有这个属性,此时系统会为这个属性自动生成设置获取方法,既然已经实现了,那么遵守者就没必要去实现协议中的这个属性了。
尽管可以实现“伪属性”,但是,我们还是应该尽量把属性定义在主接口中,而不应该定义在协议中。
还有一点,也是很重要的一点,为什么自定义的协议后面会有这么一个东西<NSObject>
?
协议也能继承。既可以继承自自定义的协议,也可以继承自系统的协议。
我们在定义协议的时候,一般都是直接继承自<NSObject>
,为什么系统要默认让协议继承自这个协议呢?
因为这个协议中定义了一些基本的方法,由于我们使用的所有类都继承NSObject
这个基类,而这个基类遵守了<NSObject>
这个协议,那么也就实现了其中的那些方法,这些方法当然可以由NSObject
及其子类对象调用,但是在不知道遵守者类型的时候需要用到id <协议名>
这样的指针,这个指针在编译期并不知道自己指向哪个对象,唯一能调用的便是协议中的方法,然而有时候又需要用一些基本的方法,比如要辨别id <协议名>
这个指针所指的对象属于哪个类,就要用到-isMemberOf:
这个方法,而这个方法是<NSObject>
这个协议中的方法之一,所以,我们自定义的协议都需要继承<NSObject>
。本段一开始便说道:<NSObject>
中的方法在NSObject
基类中实现了,那么无需再关心实现了,直接调用<NSObject>
中的方法吧。
3. protocol 可以写在哪?
写在头文件中,写在实现文件的类扩展中。
前者:可以当做是给这个类添加了一些外部接口。
后者:可以当做是给这个类添加了一些私有接口。
- 写在头文件中,类内部自然能通过
self
调用,外部也可以调用里面的方法,子类可以实现或者重写里面的方法。 - 而在类扩展中,内部可以调用,外部不能调用、子类不能重写实现和重写,相当于是私有方法。
不过,如果子类自身又遵循了这个协议,但并没有实现,那么在运行时,系统会一级级往上查找,直到找到父类的方法实现。也就是说,只要知道苹果的私有方法名,并且确保自己的类是这个私有方法所属类的子类,就可以在子类中通过只声明不实现的方式执行父类中该私有方法的实现。
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.
-
正式协议声明了一系列需要遵守协议者实现的方法。正式协议拥有它们特有的声明,遵守,类型判断的语法。你可以通过
@required
和@optional
关键词来指定哪些方法是必须实现的以及哪些方法选择实现。子类继承父类遵守的协议,正式协议也可以遵守其他协议。 -
非正式协议是基于
NSObject
的类目,其所有子类都含蓄地遵守了这个协议(类目是一种语言特性,它能够不用继承便为类添加方法)在非正式协议中的方法是可以选择实现的。在调用一个方法之前,调用者要确认目标对象是否实现了方法。在OC 2.0
引入可选正式协议方法之前,非正式协议是Foundation
和AppKit
框架中的类中实现委托的唯一方式。
类目实际上是一种特殊的协议。我们没法通过正式协议为系统的类添加方法,因为我们无法编辑系统的类。当然,我们也可以选择继承的方式,但是,这就会创建一个新的类,并不是特别划算。所以,类目这种方式派上用场了。
但是为什么系统框架中使用了这么多的类目呢?设计者当初为什么不把类目中的方法写到主接口中?
原因在于要将众多的方法打散到各处,要将同一类型功能的接口都封装在一个类目中。这样做能够减少主接口中的代码量,也便于调试。我们还可以把类中的私有方法全部封装在名为Private
的类目中,然后在实现文件中引入这个类目,这样做可以把共有的方法定义在一个类目中,也很容易能够清楚哪些是私有方法。
协议,尤其是类目,一定要给类目的名称和方法名添加自己的特有前缀,这样做既不会和系统的类目冲突,也不会在将自己的代码开源后和其他使用者的类目冲突。
6. protocol 有哪些作用,用在哪些地方
-
某一个类需要委托其他类处理某些事件,最具代表性性的便是
UITableView
的那些代理方法。这些方法其实还是代理的方法,只不过定义的地方可能会在委托者类中,通过调用这些方法,可以:将委托者中的数据传递给代理;将代理的数据传递给委托者;将委托者的事件抛给代理去处理... -
给某几个特定的类添加统一的接口,这些接口是从这些类中抽象出的共同的行为,这样便可以减少重复的代码。
总结
协议就是定义公共接口的地方,只要遵守协议,就等于在头文件中定义了这些方法,只要实现就行了。之所以有这样的设计,是因为要将共同的行为抽象出来:不同的类有不同的作用和特征,这也是面向对象的特点,但是即使千差万别,还是会有某些相似点的,这些相似的地方就可以抽象出来做成协议。但有时候这些共同的部分并不是本身就有的,而是人为的添加的,我们要求这些类具有共同的部分,而不管这些类是多么千差万别。有人会问,为什么不写一个公共的父类呢?子类继承父类,这样就能共有某些方法了?
当然是有这样的设计的,Foundation
框架下类的设计就是这样一层一层写下去的,最具相同性的属性和方法声明的位置绝对更靠前。但是,如果同时需要遵守协议的是来自两个不同继承树上的类呢?难道是找到它们共有的祖先类,然后把方法写在那里面吗?显然这么做是不行的,因为这样会导致两个继承树下的所有子类都可以调用,然而并不是所有子类都需要这些方法,所以还是得要用协议。
很多人会看到有一个<NSObject>
的协议,这个协议和NSObject
这个类同名,由NSObject
遵守,为什么不把这些方法直接写到NSObject
类中呢?因为cocoa
框架中的基类不止NSObject
一个,还有NSProxy
这样的类存在,那么<NSObject>
这个协议就很容易明白了,它抽象出了所有基类都需要的方法,为基类提供共有方法。还有一个原因的话,在上文中已经说明了,自定义的类要继承自这个协议,以供匿名对象id<协议名>
使用。