我爲何要用委託模式(Delegate)來代替消息機制

首先,消息機制並無錯,錯在咱們使用它的場景。每一種技術都有他的優勢和價值,可是不能濫用,就像繼承同樣。沒有一種技術是放之四海而皆準的,除了幾個設計原則必須遵照。前端

在討論這個話題以前,先來描述一下我在項目中被廣播消息坑過的痛苦經歷。你們知道,新聞類信息流產品會有多個頻道,每一個頻道是一個ListView或RecycleView,一個ListView裏面會有不少張卡片,每張卡片就對應一條新聞。爲方便說明問題,借用典型信息流新聞類產品的屏幕截圖來展現一下:緩存

圖中能夠直觀看出信息流產品的UI總體佈局,頂部是頻道Tab,底部是專欄Tab,點擊專欄Tab例如視頻按鈕,就會進入視頻專欄,裏面又會有不少新的頻道。每一個頻道都會有不少卡片,每張卡片上面會有一個刪除按鈕。
咱們來還原一張類圖:服務器

該類圖隱去了大量細節內容,只展現了構造大體UI所需的基本元素。
接下來咱們接到一個需求:

需求定義是當用戶點擊卡片上的刪除按鈕時,彈出一個用戶反饋彈框。以前的現狀是直接刪除卡片,而後彈出一個Toast,Toast會在兩秒後自動消失。這個彈框需求自己很簡單,可是,當我把這個彈框作出來,集成到項目中後,運行,發現兩個問題:佈局

  • 界面卡頓很厲害;
  • 點擊選項後沒法關閉。

然而我將這個彈框放在Demo中,或者項目中其它地方,都沒有問題。項目中我是這樣作的,以前是在一個刪除消息中調用doDislike方法,直接將卡片從列表中刪除。因而我在調用doDislike的地方,替換爲這個新的用戶反饋彈框,當用戶點擊其中一項後,關閉彈框,再執行doDislike方法。感受這個邏輯並無錯啊,可是事實是程序就是出現了嚴重卡頓。因而在彈框入口添加Log輸出,天啊,一瞬間執行了二十次彈框。這不科學,因而查看代碼,看看刪除流程執行過程,原來,每個ListViewController綁定了一個事件處理對象,專門用於處理來自卡片的事件,這個對象註冊了一個消息,DISLIKE_CLICK_MESSAGE,並且這個消息仍是使用的項目內廣播消息。當用戶點擊Delete按鈕的時候,卡片類將使用廣播消息接口發送DISLIKE_CLICK_MESSAGE消息,當ListViewController的事件處理對象收到這個消息時,執行doDislike方法。這樣使用消息的好處是迴避了卡片組件跟ListView之間的空間距離,參考前面的類圖,真實狀況比這個圖要複雜得多。性能

可是這帶來了一個問題,前面說過,項目中頻道是不少的,一個首頁專欄就有20幾個頻道,還有視頻專欄以及其它專欄等。因爲考慮到用戶體驗與加載性能,每每須要提早準備緩存,也就是說後臺已經建立了多個頻道對象。這些對象都將收到DISLIKE_CLICK_MESSAGE消息,他們都會執行彈框,所以就出現了剛纔描述的狀況,一瞬間二十幾個彈框,固然就會異常卡頓了,並且行爲也不正確。爲何以前的刪除就沒有問題呢,由於彈20個Toast疊加在一塊兒,文案都是同樣的,並且都是在兩秒後消失,那麼也就無法引發注意了。this

到這裏,也許你會說,列表中查詢一下數據不就好了,若是不存在待刪除的卡片數據,就不執行彈框。沒錯,我就是這樣嘗試解決問題的,前面只是前奏,如今纔是入坑經歷的開始。首先固然就是在列表中查找,找不到則不彈框,不作任何處理,至關於讓消息發動機空轉20轉。覺得這樣就大功告成了,實測發現,依然會出現連續兩次彈框。繼續排查緣由,由於首頁跟頻道列表頁是共用了一份數據,也就是說有兩個ListViewController裏面是相同的數據,所以出現問題,並且即使不考慮首頁數據重複問題,誰又能保證服務器不在兩個頻道下發同一張卡片呢,咱們客戶端程序總不能靠天吃飯吧,總的想辦法解決問題才行。我注意到ListViewController有一個selected狀態,一個專欄應該只有一個頻道處於選中狀態,那麼咱們是否能夠用這個狀態來判斷,當前處於選中狀態的ListView才響應刪除消息,理論上能夠,可是也要考慮一個狀況,除了首頁新聞專欄,還有視頻專欄和其它專欄,每一個專欄均可能有一個頻道處於selected狀態。要完全解決這個問題,還應該引入一個isActive狀態,當專欄處於不可見,被其它專欄覆蓋的時候,isActive設爲false,最前端專欄isActive設爲true,那麼當收到消息的時候,檢測selected和isActive狀態,二者同時爲true,才能處理DISLIKE消息。設計

當我試圖採用上述方案解決問題的時候,問題來了,若是是全新設計一個系統,一開始就將這些狀態規劃好,天然是沒有問題的,可是要在一個已經很龐大的系統上去新添加一種系統性狀態規則,難度可想而知,成本很高不說,還很容易出現問題,一旦狀態維護很差,又將是無休止的埋坑、踩坑、填坑的過程。cdn

雖然想象起來很高大上,但我仍是及時踩住了剎車,簡單的嘗試一下後就終止了該方案。視頻

新的方案:DISLIKE事件的處理再也不使用消息機制。將卡片組件、卡片、ListView、事件處理對象等,使用清晰的鏈條連接起來。由於這些對象是有關係鏈的,有明確的生命期管理關係(雖然有垃圾回收不用考慮什麼時候銷燬)。咱們徹底可使用委託模式(Delegate)在Owner中處理來自子孫產生的回調事件。咱們的系統中原本就有UIEventDelegate接口設計,跟消息機制的差異,主要是從ListView到卡片組件DeleteButton之間,必需要層層設置Delegate回調對象,事件發生時層層上報。Owner在建立子對象時調用obj.setEventHandler(this),任何對象在產生事件時要麼本身消化,要麼只能將事件回調給Owner處理,並非經過消息一拋了之,惟一的缺點是構造對象時傳遞Delegate回調接口略顯麻煩。不過這樣一來,事件的傳遞是清晰的,誰產生事件,誰處理事件,流程是很清楚的,不會再有亂子發生,再不會有其它不相干對象收到事件的狀況出現。對象

經過這種方案最後圓滿解決問題,可是嘗試各類方案解決這個問題的過程,我足足花了兩天時間,發現問題、排查問題、嘗試方案到解決問題,整個過程遠比我這裏描述的要複雜的多。要知道以前實現彈框這一步需求一共才花了3天,其中仍是由於作了一個通用的Popover組件。那麼,試問這兩天解Bug的時間花的值得嗎?

總結:事件傳遞應該有明確清晰的鏈條,而不是隨意亂飛,能不能正確處理全看緣分。

像內存報警、應用換膚、多語言切換這些很是通用,而又不存在各業務間關聯的事件通知,使用廣播消息是很是方便的,也是合理的。可是一旦在複雜的業務邏輯中濫用消息機制尤爲是廣播消息,其後果將是災難性的。

相關文章
相關標籤/搜索