在iOS
開發(fā)中减俏,Protocol
是一種經(jīng)常用到的設(shè)計(jì)模式,蘋果的系統(tǒng)框架中也普遍用到了這種方式战转,比如UITableView
中的<UITableViewDelegate>
痛黎,以及<NSCopying>
予弧、<NSObject>
這樣的協(xié)議。我想大家也都自定義過協(xié)議湖饱,一般都用于回調(diào)掖蛤,或者數(shù)據(jù)傳遞。不過井厌,用久了以后蚓庭,不知道大家是否會(huì)有一些困惑:協(xié)議只能用于委托代理嗎?為什么系統(tǒng)中定義了這么多協(xié)議仅仆?為什么感覺分類和協(xié)議好像器赞?系統(tǒng)中又為何會(huì)定義這么多分類?被這些問題轟炸后墓拜,我不禁又開始想拳魁,協(xié)議到底是個(gè)啥玩意?
帶著這樣的目地撮弧,我閱讀了一些文檔書籍潘懊,最后整理了以下的內(nèi)容:
-
protocol
是什么? -
protocol
里有些啥? -
protocol
可以寫在哪? -
protocol
里的方法由誰實(shí)現(xiàn)姚糊,由誰調(diào)用? -
protocol
分類:正式協(xié)議和非正式協(xié)議? -
protocol
有哪些作用授舟,能用在哪些地方?
1. protocol 是什么?
iOS 開發(fā)文檔是這樣定義的:(翻譯略渣救恨,請(qǐng)見諒)
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.
協(xié)議聲明了任何類都能夠選擇實(shí)現(xiàn)的程序接口。協(xié)議能夠使兩個(gè)不同繼承樹上的類相互交流并完成特定的目的释树,因此它提供了除繼承外的另一種選擇肠槽。任何能夠?yàn)槠渌愄峁┯杏眯袨榈念惗寄軌蚵暶鹘涌趤砟涿膫鬟_(dá)這個(gè)行為。任何其他類都能夠選擇遵守這個(gè)協(xié)議并實(shí)現(xiàn)其中的一個(gè)或多個(gè)方法奢啥,從而利用這個(gè)行為秸仙。如果協(xié)議遵守者實(shí)現(xiàn)了協(xié)議中的方法,那么聲明協(xié)議的類就能夠通過遵守者調(diào)用協(xié)議中的方法桩盲。
2. protocol 里有些啥?
協(xié)議中能夠聲明方法寂纪,以及屬性。然后問題就來了赌结,不是不能定義成員變量的嗎捞蛋?
對(duì),的確不能定義成員變量柬姚,但是屬性是什么拟杉?屬性包含了三個(gè)東西:成員變量、setter
方法量承、getter
方法搬设。在類中定義的屬性,當(dāng)然三者都有撕捍,然而協(xié)議中定義的屬性只有獲取和設(shè)置方法拿穴,沒有成員變量,這就要求該協(xié)議的遵守者必須自己寫出setter
和getter
方法的實(shí)現(xiàn)卦洽。但是有一種情況是不需要的,那就是遵守者本來就有這個(gè)屬性斜棚,此時(shí)系統(tǒng)會(huì)為這個(gè)屬性自動(dòng)生成設(shè)置獲取方法阀蒂,既然已經(jīng)實(shí)現(xiàn)了,那么遵守者就沒必要去實(shí)現(xiàn)協(xié)議中的這個(gè)屬性了弟蚀。
盡管可以實(shí)現(xiàn)“偽屬性”蚤霞,但是,我們還是應(yīng)該盡量把屬性定義在主接口中义钉,而不應(yīng)該定義在協(xié)議中昧绣。
還有一點(diǎn),也是很重要的一點(diǎn)捶闸,為什么自定義的協(xié)議后面會(huì)有這么一個(gè)東西<NSObject>
?
協(xié)議也能繼承夜畴。既可以繼承自自定義的協(xié)議拖刃,也可以繼承自系統(tǒng)的協(xié)議。
我們?cè)诙x協(xié)議的時(shí)候贪绘,一般都是直接繼承自<NSObject>
兑牡,為什么系統(tǒng)要默認(rèn)讓協(xié)議繼承自這個(gè)協(xié)議呢?
因?yàn)檫@個(gè)協(xié)議中定義了一些基本的方法税灌,由于我們使用的所有類都繼承NSObject
這個(gè)基類均函,而這個(gè)基類遵守了<NSObject>
這個(gè)協(xié)議,那么也就實(shí)現(xiàn)了其中的那些方法菱涤,這些方法當(dāng)然可以由NSObject
及其子類對(duì)象調(diào)用苞也,但是在不知道遵守者類型的時(shí)候需要用到id <協(xié)議名>
這樣的指針,這個(gè)指針在編譯期并不知道自己指向哪個(gè)對(duì)象粘秆,唯一能調(diào)用的便是協(xié)議中的方法如迟,然而有時(shí)候又需要用一些基本的方法,比如要辨別id <協(xié)議名>
這個(gè)指針?biāo)傅膶?duì)象屬于哪個(gè)類翻擒,就要用到-isMemberOf:
這個(gè)方法氓涣,而這個(gè)方法是<NSObject>
這個(gè)協(xié)議中的方法之一,所以陋气,我們自定義的協(xié)議都需要繼承<NSObject>
劳吠。本段一開始便說道:<NSObject>
中的方法在NSObject
基類中實(shí)現(xiàn)了,那么無需再關(guān)心實(shí)現(xiàn)了巩趁,直接調(diào)用<NSObject>
中的方法吧痒玩。
3. protocol 可以寫在哪?
寫在頭文件中议慰,寫在實(shí)現(xiàn)文件的類擴(kuò)展中蠢古。
前者:可以當(dāng)做是給這個(gè)類添加了一些外部接口。
后者:可以當(dāng)做是給這個(gè)類添加了一些私有接口别凹。
- 寫在頭文件中草讶,類內(nèi)部自然能通過
self
調(diào)用,外部也可以調(diào)用里面的方法炉菲,子類可以實(shí)現(xiàn)或者重寫里面的方法堕战。 - 而在類擴(kuò)展中,內(nèi)部可以調(diào)用拍霜,外部不能調(diào)用嘱丢、子類不能重寫實(shí)現(xiàn)和重寫,相當(dāng)于是私有方法祠饺。
不過越驻,如果子類自身又遵循了這個(gè)協(xié)議,但并沒有實(shí)現(xiàn),那么在運(yùn)行時(shí)缀旁,系統(tǒng)會(huì)一級(jí)級(jí)往上查找记劈,直到找到父類的方法實(shí)現(xiàn)。也就是說诵棵,只要知道蘋果的私有方法名抠蚣,并且確保自己的類是這個(gè)私有方法所屬類的子類,就可以在子類中通過只聲明不實(shí)現(xiàn)的方式執(zhí)行父類中該私有方法的實(shí)現(xiàn)履澳。
4. protocol 里的方法由誰實(shí)現(xiàn)嘶窄,由誰調(diào)用
實(shí)現(xiàn):遵守協(xié)議者及其子類
調(diào)用:遵守協(xié)議者、其子類距贷、id <協(xié)議名>
5. protocol 的分類:正式協(xié)議 和 非正式協(xié)議(類別)
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.
正式協(xié)議聲明了一系列需要遵守協(xié)議者實(shí)現(xiàn)的方法柄冲。正式協(xié)議擁有它們特有的聲明,遵守忠蝗,類型判斷的語法现横。你可以通過
@required
和@optional
關(guān)鍵詞來指定哪些方法是必須實(shí)現(xiàn)的以及哪些方法選擇實(shí)現(xiàn)。子類繼承父類遵守的協(xié)議阁最,正式協(xié)議也可以遵守其他協(xié)議戒祠。非正式協(xié)議是基于
NSObject
的類目,其所有子類都含蓄地遵守了這個(gè)協(xié)議(類目是一種語言特性速种,它能夠不用繼承便為類添加方法)在非正式協(xié)議中的方法是可以選擇實(shí)現(xiàn)的姜盈。在調(diào)用一個(gè)方法之前,調(diào)用者要確認(rèn)目標(biāo)對(duì)象是否實(shí)現(xiàn)了方法配阵。在OC 2.0
引入可選正式協(xié)議方法之前馏颂,非正式協(xié)議是Foundation
和AppKit
框架中的類中實(shí)現(xiàn)委托的唯一方式。
類目實(shí)際上是一種特殊的協(xié)議棋傍。我們沒法通過正式協(xié)議為系統(tǒng)的類添加方法救拉,因?yàn)槲覀儫o法編輯系統(tǒng)的類。當(dāng)然瘫拣,我們也可以選擇繼承的方式亿絮,但是,這就會(huì)創(chuàng)建一個(gè)新的類麸拄,并不是特別劃算派昧。所以,類目這種方式派上用場(chǎng)了感帅。
但是為什么系統(tǒng)框架中使用了這么多的類目呢斗锭?設(shè)計(jì)者當(dāng)初為什么不把類目中的方法寫到主接口中地淀?
原因在于要將眾多的方法打散到各處失球,要將同一類型功能的接口都封裝在一個(gè)類目中。這樣做能夠減少主接口中的代碼量,也便于調(diào)試实苞。我們還可以把類中的私有方法全部封裝在名為Private
的類目中豺撑,然后在實(shí)現(xiàn)文件中引入這個(gè)類目,這樣做可以把共有的方法定義在一個(gè)類目中黔牵,也很容易能夠清楚哪些是私有方法聪轿。
協(xié)議,尤其是類目猾浦,一定要給類目的名稱和方法名添加自己的特有前綴陆错,這樣做既不會(huì)和系統(tǒng)的類目沖突,也不會(huì)在將自己的代碼開源后和其他使用者的類目沖突金赦。
6. protocol 有哪些作用音瓷,用在哪些地方
某一個(gè)類需要委托其他類處理某些事件,最具代表性性的便是
UITableView
的那些代理方法夹抗。這些方法其實(shí)還是代理的方法绳慎,只不過定義的地方可能會(huì)在委托者類中,通過調(diào)用這些方法漠烧,可以:將委托者中的數(shù)據(jù)傳遞給代理杏愤;將代理的數(shù)據(jù)傳遞給委托者;將委托者的事件拋給代理去處理...給某幾個(gè)特定的類添加統(tǒng)一的接口已脓,這些接口是從這些類中抽象出的共同的行為珊楼,這樣便可以減少重復(fù)的代碼。
總結(jié)
協(xié)議就是定義公共接口的地方摆舟,只要遵守協(xié)議亥曹,就等于在頭文件中定義了這些方法,只要實(shí)現(xiàn)就行了恨诱。之所以有這樣的設(shè)計(jì)媳瞪,是因?yàn)橐獙⒐餐男袨槌橄蟪鰜恚翰煌念愑胁煌淖饔煤吞卣鳎@也是面向?qū)ο蟮奶攸c(diǎn)照宝,但是即使千差萬別蛇受,還是會(huì)有某些相似點(diǎn)的,這些相似的地方就可以抽象出來做成協(xié)議厕鹃。但有時(shí)候這些共同的部分并不是本身就有的兢仰,而是人為的添加的,我們要求這些類具有共同的部分剂碴,而不管這些類是多么千差萬別把将。有人會(huì)問,為什么不寫一個(gè)公共的父類呢忆矛?子類繼承父類察蹲,這樣就能共有某些方法了请垛?
當(dāng)然是有這樣的設(shè)計(jì)的,Foundation
框架下類的設(shè)計(jì)就是這樣一層一層寫下去的洽议,最具相同性的屬性和方法聲明的位置絕對(duì)更靠前宗收。但是,如果同時(shí)需要遵守協(xié)議的是來自兩個(gè)不同繼承樹上的類呢亚兄?難道是找到它們共有的祖先類混稽,然后把方法寫在那里面嗎?顯然這么做是不行的审胚,因?yàn)檫@樣會(huì)導(dǎo)致兩個(gè)繼承樹下的所有子類都可以調(diào)用匈勋,然而并不是所有子類都需要這些方法,所以還是得要用協(xié)議膳叨。
很多人會(huì)看到有一個(gè)<NSObject>
的協(xié)議颓影,這個(gè)協(xié)議和NSObject
這個(gè)類同名,由NSObject
遵守懒鉴,為什么不把這些方法直接寫到NSObject
類中呢诡挂?因?yàn)?code>cocoa框架中的基類不止NSObject
一個(gè),還有NSProxy
這樣的類存在临谱,那么<NSObject>
這個(gè)協(xié)議就很容易明白了璃俗,它抽象出了所有基類都需要的方法,為基類提供共有方法悉默。還有一個(gè)原因的話城豁,在上文中已經(jīng)說明了,自定義的類要繼承自這個(gè)協(xié)議抄课,以供匿名對(duì)象id<協(xié)議名>
使用唱星。