從QMAction的實(shí)現(xiàn)來看協(xié)議與繼承

先說下背景:

我們想通過一種統(tǒng)一的跳轉(zhuǎn)鏈接杨赤,來實(shí)現(xiàn)H5、原生及端外跳轉(zhuǎn)到端內(nèi)等幾種場景下的統(tǒng)一跳轉(zhuǎn)。這就涉及到頁面跳轉(zhuǎn)組件风皿。這些跳轉(zhuǎn)組件的設(shè)計(jì)比較考驗(yàn)開發(fā)人員的能力,如何做才能減少頁面間的耦合匠璧,提高模塊化呢桐款?本文將為你講述一種實(shí)現(xiàn)思路,至于優(yōu)劣夷恍,見仁見智魔眨。

首先。我們的跳轉(zhuǎn)鏈接大致如下:?

qimi://juanpi?type=1&content={"a":"ssssss","b":"gggg"}

這里面type是跳轉(zhuǎn)頁面的類型酿雪,content是需要的參數(shù)遏暴,用json形式組裝,由各個(gè)頁面自己解析指黎。

一朋凉、原始想法

最開始我的想法是這樣的:

? ? ?首先,所有跳轉(zhuǎn)都得經(jīng)過一個(gè)類醋安,這個(gè)類里面得包含所有需要跳轉(zhuǎn)的頁面杂彭,然后根據(jù)參數(shù)來區(qū)分具體跳轉(zhuǎn)到哪里。這個(gè)類我們暫定為QMAction茬故,核心的方法大概是這樣的:

-(void)handleJumpWithType:(NSInteger)type withParams:(NSDictionary:)params ?jumpController:(UINavigationController*)controller?{ ? ?

? ? ?UIViewController *destVC;

? ? ?if (type == 1) //首頁?{

? ? ? ? ? ?destVC = [[HomeViewController alloc] init];

? ? ? } else if(type == 2) //列表頁面 {

? ? ? ? ? ? destVC = [[ListViewController alloc] init];

? ? ? }?

? ? ? .........//還有很多類似的判斷

? ? ? if(destVC) {

? ? ? ? ? ? [controller pushViewController:destVC];

? ? ? }

}

但是這么做有幾個(gè)問題:

1. 首先盖灸,跳轉(zhuǎn)的來源包括H5頁面、外部app磺芭、推送或者任何本地頁面赁炎,有的情況下需要push一個(gè)新頁面,有的情況需要先pop回首頁再push钾腺,如何適應(yīng)多樣化的需求徙垫?

2. 每次新增type,就要多引用一個(gè)頭文件放棒,即引用依賴過多姻报。

3. 創(chuàng)建VC的代碼大部分都是重復(fù)的,這種重復(fù)的alloc init 實(shí)際上是在扼殺編程的熱情间螟。我們需要減少重復(fù)代碼吴旋。

基于這幾個(gè)問題损肛,我們嘗試重新設(shè)計(jì)頁面跳轉(zhuǎn)組件。

二荣瑟、解決引用依賴

熟悉OC的朋友都知道治拿,OC有一個(gè)動(dòng)態(tài)運(yùn)行機(jī)制,所有的類和方法都可以通過字符串來獲得笆焰,比如NSSelectorFromString,NSClassFromString劫谅。如果我用這個(gè)方法,讓外部把類名傳遞進(jìn)來嚷掠,是不是就不用那么多頭文件了呢捏检?而且這樣做if...else if...else 這種判斷也大大減少了。

? ? 抱著這個(gè)想法不皆,我嘗試了修改handleJumpWithType:params:jumpController:這個(gè)方法贯城,確實(shí)是可以動(dòng)態(tài)的去獲取類,但是有一個(gè)問題粟焊,有的VC類名特別長冤狡,而且在推送和H5頁面跳轉(zhuǎn)的場景下,得針對安卓和ios做區(qū)分项棠,因?yàn)閮蛇叺念惷⒉幌嗤āS谑牵@個(gè)方案很快就被否決了香追。那么合瓢,還有沒有其它的方式呢?

? ?可不可以在QMAction內(nèi)部做一個(gè)映射呢透典?通過一個(gè)字典獎(jiǎng)type和類名對應(yīng)起來晴楔,這樣后端只需要知道某個(gè)頁面對應(yīng)的type,就可以設(shè)置跳轉(zhuǎn)鏈接了峭咒。這個(gè)方法有一定的靈活性税弃,但還是面臨一個(gè)問題,就是當(dāng)類名改了以后凑队,這個(gè)映射的字典里面也得做對應(yīng)的修改则果,如果開發(fā)人員忘記改了,就跳轉(zhuǎn)不了了漩氨。這種方式維護(hù)起來還是不夠簡單西壮,而且還是需要引用頭文件。

? ? ?那么叫惊,能不能用插件的方式呢款青?從之前了解過的一些瀏覽器插件管理器的實(shí)現(xiàn)方式來看,我們可以將每一個(gè)頁面當(dāng)作一個(gè)插件霍狰,通過輪詢的方式來得到哪一個(gè)插件能響應(yīng)我需要調(diào)用的方法抡草。他的代碼大概是這樣的:

for (Plugin * plugin in self.plugins) {

? ? if([plugin respondToSelector:NSSelectorFromString(xxxx)]) {

? ? ? ? [plugin performSelector:NSSelectorFromString(xxxx)];

? ? ? ? ?break;

? ? }

}

這里的前提是饰及,每一個(gè)插件都繼承自Plugin的基類。而self.plugins數(shù)組里面包含的就是所有的插件渠牲,這些插件是在程序啟動(dòng)的時(shí)候通過代碼或者plist文件添加到這個(gè)數(shù)組里面的旋炒。

我們把每一個(gè)頁面當(dāng)做一個(gè)插件來處理,在QMActon中只需要引入一個(gè)Plugin基類签杈,再在各個(gè)類中加一個(gè)方法,判斷能不能處理跳轉(zhuǎn)的請求鼎兽,這樣不就大大減少了引用的文件了嗎答姥?

三、協(xié)議與遍歷

通過第二步的改良谚咬,其實(shí)我們已經(jīng)算是解決了引用依賴過多的問題鹦付,但是這樣做代碼的侵入性還是有點(diǎn)高,因?yàn)槊總€(gè)VC都得繼承自同一個(gè)基類择卦,這對于之前沒有共同基類的VC來說改動(dòng)還是有點(diǎn)大敲长,那么能不能在不改變原來類的繼承關(guān)系的條件下來實(shí)現(xiàn)呢?

這時(shí)候我想到了協(xié)議(或者說代理)秉继。

協(xié)議是目前所有重構(gòu)方案里面代碼侵入性最小的祈噪,一不需要改變基類,對協(xié)議的宿主(也就是實(shí)現(xiàn)協(xié)議的類)沒有類型上的要求尚辑,方便移植;二不強(qiáng)制要求宿主類必須實(shí)現(xiàn)某個(gè)方法(協(xié)議方法分為可選和必選兩種)。

比如之前那段輪詢的代碼昼扛,用協(xié)議實(shí)現(xiàn)起來是這樣的:

for (id * plugin in self.plugins) {

? ? if([id<QMActionProtocol> respondToSelector:NSSelectorFromString(xxxx)]) ? ? {

? ? ? ?[id<QMActionProtocol> performSelector:NSSelectorFromString(xxxx)];

? ? ? ?break;

? ? }

}

這樣瞭恰,我并不需要引用具體的子類,是不是看起來好一點(diǎn)呢瓢喉?

但是宁赤,還有一個(gè)問題,如果我要像H5插件那樣在程序啟動(dòng)時(shí)把所有插件類注冊一遍栓票,未免有些low了决左,這種引用依賴關(guān)系或多或少還是存在一點(diǎn)。有沒有更徹底的辦法呢逗载?

這時(shí)候哆窿,就要用到runtime了。

我們知道runtime可以動(dòng)態(tài)判斷一個(gè)類有沒有實(shí)現(xiàn)一個(gè)方法厉斟,那他應(yīng)該也可以判斷一個(gè)類有沒有實(shí)現(xiàn)一個(gè)協(xié)議挚躯。帶著疑問,我查閱了一下runtime的文檔擦秽,找到了這個(gè)方法:

OBJC_EXPORT BOOL class_conformsToProtocol(Class cls, Protocol *protocol)

有了這個(gè)方法码荔,我就可以不需要自己去寫代碼一行行注冊插件了漩勤,只需要先遍歷系統(tǒng)的所有類,然后判斷每個(gè)類是否實(shí)現(xiàn)了這個(gè)協(xié)議就可以了缩搅。


遍歷獲取某個(gè)協(xié)議的類

為了減少每次遍歷的性能消耗(類越多越败,消耗越大),我們可以用一個(gè)數(shù)組把這些類緩存下來硼瓣,下次直接從數(shù)組里面讀出究飞。

四、協(xié)議的具體實(shí)現(xiàn)

前面只是通過運(yùn)行時(shí)的方法拿到了所有類堂鲤,但是輪詢是避免不了的亿傅,我們需要對每一個(gè)類做判斷,是否對應(yīng)了我們的type瘟栖。另外葵擎,還需要一個(gè)方法來創(chuàng)建VC,以及另一個(gè)方法來做一些特殊處理

協(xié)議方法

每個(gè)遵循了QMActionProtocol的類需要實(shí)現(xiàn)這些協(xié)議方法(1必須實(shí)現(xiàn)半哟,2酬滤、3可選),其中QMAction里面就含有type和content寓涨。 同一個(gè)類可以對應(yīng)多個(gè)type盯串,但一個(gè)type只能對應(yīng)一個(gè)類,否則系統(tǒng)就會根據(jù)遍歷的順序跳到優(yōu)先跳到第一個(gè)對應(yīng)這個(gè)type的類缅茉,而這個(gè)順序是隨機(jī)的嘴脾。

當(dāng)所有類都輪詢過一遍之后,就有了type和class的對應(yīng)關(guān)系蔬墩,而這個(gè)關(guān)系以后會頻繁用到译打,所以我們可以用NSCache(一種Key-Value的容器,類似NSDictionary拇颅,但內(nèi)存不足時(shí)會自動(dòng)釋放)緩存下來奏司。

創(chuàng)建VC的那個(gè)方法所做的就是解析content,給VC初始化并對相應(yīng)屬性賦值樟插,對于一些簡單的VC韵洋,只需要一句

id viewcontroller = ?[[[self class] alloc] init];

就可以了。

參數(shù)的賦值可以通過字典和KVC 解決黄锤,我們將自定義的一個(gè)字典對象當(dāng)作參數(shù)容器傳給VC搪缨,VC拿到后,調(diào)用setValuesForObject 方法鸵熟,把字典的key當(dāng)作屬性名稱副编,value當(dāng)作屬性值,直接賦值流强。

KVC賦值


其中一個(gè)例子:

協(xié)議方法例子


五痹届、子類化QMAction與默認(rèn)值

前面說的是如何運(yùn)用協(xié)議和運(yùn)行時(shí)來解決依賴和耦合的問題呻待,現(xiàn)在要解決的另一個(gè)問題是如何隱藏不同類之間的差異。

我們知道QMAction是一個(gè)跳轉(zhuǎn)組件队腐, 一般情況下需要?jiǎng)?chuàng)建一個(gè)VC蚕捉,然后跳轉(zhuǎn)過去, 但是有些特殊場景是不需要?jiǎng)?chuàng)建VC的柴淘,比如說在當(dāng)前VC上添加一個(gè)view迫淹,或者分享某個(gè)頁面到第三方平臺,這種看似差不多的功能實(shí)際上處理起來很不一樣悠就。為了讓QMAction能適應(yīng)所有的場景千绪,就需要統(tǒng)一調(diào)用方式,隱藏內(nèi)部的差異梗脾。

我們把QMAction拆成了三類,QMPushAction, QMShareAction和QMCustomAction盹靴。三個(gè)類分別對應(yīng)普通的跳轉(zhuǎn)炸茧、分享以及自定義的場景(如彈起浮層,關(guān)閉某個(gè)頁面)等稿静。這三個(gè)類共同繼承自QMAction梭冠。同時(shí),在類的內(nèi)部改备,我們加上了一段代碼指定某個(gè)type對應(yīng)的默認(rèn)class控漠。這么做的好處是不用在調(diào)用的代碼里顯式申明action的類型,在H5悬钳、外部app等非原生代碼調(diào)用的場景下能自動(dòng)匹配到對應(yīng)的類型盐捷。

QMAction默認(rèn)實(shí)現(xiàn)


同樣的技巧我們也用在了QMPushAction的transitionStyle上,這個(gè)屬性指定了跳轉(zhuǎn)的方式默勾,如Push碉渡,Pop,Present等母剥。除非是調(diào)用的時(shí)候顯式的指定了transitionStyle滞诺,否則以下這些type會按默認(rèn)的跳轉(zhuǎn)方式來執(zhí)行,這在H5頁面跳轉(zhuǎn)到原生頁面時(shí)特別有效环疼。這里transitionStyle的類型用了NSNumber而不是NSInteger 是因?yàn)镹SNumber默認(rèn)是nil习霹,可以使用懶加載,而NSInteger不行炫隶。

跳轉(zhuǎn)方式默認(rèn)實(shí)現(xiàn)


下面是使用transitionStyle的代碼:

使用transitionStyle
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淋叶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子等限,更是在濱河造成了極大的恐慌爸吮,老刑警劉巖芬膝,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異形娇,居然都是意外死亡锰霜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門桐早,熙熙樓的掌柜王于貴愁眉苦臉地迎上來癣缅,“玉大人,你說我怎么就攤上這事哄酝∮汛妫” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵陶衅,是天一觀的道長屡立。 經(jīng)常有香客問我,道長搀军,這世上最難降的妖魔是什么膨俐? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮罩句,結(jié)果婚禮上焚刺,老公的妹妹穿的比我還像新娘。我一直安慰自己门烂,他們只是感情好乳愉,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屯远,像睡著了一般蔓姚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氓润,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天赂乐,我揣著相機(jī)與錄音,去河邊找鬼咖气。 笑死挨措,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崩溪。 我是一名探鬼主播浅役,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼伶唯!你這毒婦竟也來了觉既?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞪讼,沒想到半個(gè)月后钧椰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡符欠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年嫡霞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片希柿。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诊沪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出曾撤,到底是詐尸還是另有隱情端姚,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布挤悉,位于F島的核電站渐裸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏装悲。R本人自食惡果不足惜橄仆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衅斩。 院中可真熱鬧,春花似錦怠褐、人聲如沸畏梆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奠涌。三九已至,卻和暖如春磷杏,著一層夾襖步出監(jiān)牢的瞬間溜畅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工极祸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慈格,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓遥金,卻偏偏與公主長得像浴捆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子稿械,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容