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方法。在类中定义的属性,当然三者都有,然而协议中定义的属性只有获取和设置方法,没有成员变量,这就要求该协议的遵守者必须自己写出settergetter方法的实现。但是有一种情况是不需要的,那就是遵守者本来就有这个属性,此时系统会为这个属性自动生成设置获取方法,既然已经实现了,那么遵守者就没必要去实现协议中的这个属性了。

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

还有一点,也是很重要的一点,为什么自定义的协议后面会有这么一个东西<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引入可选正式协议方法之前,非正式协议是FoundationAppKit框架中的类中实现委托的唯一方式。

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

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

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

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

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

  • 某一个类需要委托其他类处理某些事件,最具代表性性的便是UITableView的那些代理方法。这些方法其实还是代理的方法,只不过定义的地方可能会在委托者类中,通过调用这些方法,可以:将委托者中的数据传递给代理;将代理的数据传递给委托者;将委托者的事件抛给代理去处理...

  • 给某几个特定的类添加统一的接口,这些接口是从这些类中抽象出的共同的行为,这样便可以减少重复的代码。

总结

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,679评论 0 9
  • 1.项目经验 2.基础问题 3.指南认识 4.解决思路 ios开发三大块: 1.Oc基础 2.CocoaTouch...
    阳光的大男孩儿阅读 4,969评论 0 13
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,125评论 29 470
  • 132.转换错误成可选值 通过转换错误成一个可选值,你可以使用 try? 来处理错误。当执行try?表达式时,如果...
    无沣阅读 1,237评论 0 3
  • t Running. His work has been translated into more than fo...
    夏至末日阅读 166评论 0 0