在 ios 開發中,uitableview 可以說是最常用的控制項。幾行**,實現對應方法,系統就會給你呈現乙個 60 幀無比流暢的列表,讓初學者成就感爆棚。然而隨著開發的深入,我們就會慢慢覺察到當前的 uitableview 實現會有這樣或那樣的問題。
幾乎所有 tableview adapter 中都有如下的**registerclass(nib):forcellreuseidentifier
進行 cell 重用的註冊,後續又需要使用dequeuereusablecellwithidentifier:
獲取對應 cell。蘋果的這套重用機制對於開發者來說相當簡單友好,但寫多了難免覺得重複乏味。同時如何給 cell 設定乙個有意義且不重複的 reuseidentifier 又會成為眾多強迫症程式設計師的煩惱之一。
隨著業務深入,乙個 uitableview 往往會包含多種 model,對應不同形式的 cell,那麼建立 model 和 cell 的對映關係就會非常蛋疼,無論是if else,switch,還是 map都不是那麼的優雅,每當 model 型別有所增刪,開發者往往需要心驚膽戰地檢查各處實現方法裡是否進行了正確的處理。
業務繼續深入,為了保證相關**整潔,易於拓展和效能高效,除了維護 model 和 cell 關係(modelcellmap
)外,我們往往需要引入各種類做職責分離:datasource
管理資料來源,layoutmanager
負責排版和提供預計算高度能力,cellheightcache
提供高度快取,interactor
提供事件路由和處理等等,這樣可以一定程度減輕**膨脹的問題。但也不是完美的:套路都是類似的,即使你熟練掌握了這些所謂的設計原則,在實際操作中仍有大量的重複**。
當 model 變化時,我們往往需要通過當前 model 位置反推出 cell 在 uitableview 中的位置(即indexpath
),然後做相應的更新處理,反之亦然。但這部分工作無非是陣列遍歷,尋找 index,重複且繁瑣,稍有不慎還有出錯導致崩潰的可能。
為了解決如上問題,同時也受到 iglistkit 和 react.js 的啟發,m80tableviewcomponent 提出了一種元件化的解決方案,實現類似 react.js 的 「單向資料繫結」 功能,同時將大量的重複計算歸納在元件內部,上層使用者只需要根據當前業務建立相應元件並組合使用即可。
為了實現整個 uitableview 的流程, m80tableviewcomponent 引入三個基礎元件:
顧名思義,他們分別對應 uitableview,section 和 uitableviewcell。用前端技術做模擬的話,m80tableviewcomponent 就是我們定義的 virtualdom,而 uitableview 則是真正的 dom。前者記錄虛擬的層次結構,後者仍負責最終的渲染。具體關係參考下圖:
乙個簡單的 m80tableviewcomponent 定義如下
這是乙個用於文字列表顯示的元件,只實現最基本元件協議
定義完元件後,我們只需要按照順序將元件加入父元件中,即可完成和 uitableview 的繫結。
具體效果詳見 example project
看完上述的使用方式後,你很可能將 m80tableviewcomponent 當成一種固定資料來源組裝方式而已,並沒有其他新意。但事實上,除了充當固定結構資料來源外,它還有如下優勢
當我們使用元件時,一旦當前 m80tableviewcomponent 和 uitableview 關聯,後續針對 m80tableviewcomponent 的所有操作都會實時反應到 uitableview 之上,包括對 cell component 的移除,重新整理,插入,以及 section component 的插入,移除和重新整理。我們不再需要繁瑣地通過 controller 同時操作 view 和 model 以保證其一致性,只需要單純操作 component 即可:component 將根據自身層次結構計算出對應的 ui 層次結構,在修改 component 內部結構的同時也會自動獲取到對應的 cell 物件進行修改。這樣做的好處是上層開發只需要關注 component 即可,而不再關心 indexpath 相關的計算過程,從而規避繁複的 indexpath 計算及計算錯誤導致的崩潰。
使用 m80tableviewcomponent 可以輕易支援多種不同型別的資料模型,同時由於我們將復用層次從 vc/tableview 下降到 cell/section component 層次,也更方便了在不同場景下的組合使用。
每乙個 m80tableviewcellcomponent 在第一次被使用時都會通過m80tableviewcomponentregister
根據上下文資訊自動繫結 reuseidentifier 和 cellclass 的關係,完成 cell 的重用。預設使用當前 cell component 的類名作為 reuseidentifier,既能保證不與其他 cell 重名,又省去了取名之苦。
在 ios 中比較蛋疼的事情是如何判斷兩個物件相等:在不使用 runtime 的場景下,往往需要業務層新增大量冗餘**用於支援物件比較,而使用了 runtime 又會對業務侵入過多。在 m80tableviewcomponent 中我們使用了一種不基於 runtime 且比較輕量的方法:
所有的 m80tableviewcellcomponent 都遵循 m80listdiffable 協議,以用於元件內部的一致性判斷:
預設情況下,每個 cell component 在初始化時都會有自己唯一的 cellidentifier 作為 diffablehash。
以此為出發點,我們就可以進行如下場景的優化。
當開啟高度快取選項時,m80tableviewcomponent 計算 cell 高度後會自動記錄 diffablehash 和 height 的對應關係。後續再次重新整理將自動獲取對應高度而無需再次計算。當乙個 cell 有多重狀態,需要在不同狀態下展示不同高度時,則可以通過業務狀態返回不同的 diffablehash 進行高度切換。除了高度快取外,m80tableviewcomponent 也提供了一種預計算高度的機制,在組裝完 cell component 後,只需要簡單呼叫基類方法measure
就可以直接完成預計算。
而適用區域性重新整理時,cell component 的 diffablehash 將做為唯一標識:old components 和 new components 根據 diffablehash 被 hash 到不同桶內,衝突桶中的 component 標記為 move,不衝突桶中的 component 則為 add/remove。詳細演算法可參考 m80listdiff 函式。在合適的場景下,使用 listdiff 進行 section 的重新載入,而不是人工計算各種變化資訊後進行逐一操作,能夠在保證效能的前提下,簡化開發過程和良好的介面表現。
不同於以往構建 uitableview 的常見用法,使用 m80tableviewcomponent 推薦所有操作都針對 component 進行。
快取引入的元件 先更新資料庫,還是快取?
這一篇來聊聊快取一致性的問題,這裡討論的範圍有限,僅僅是應用快取與後端儲存的一致性,當然也會適當做下延伸 如下 4 種組合,該如何決策?標準在 一致性問題出在哪?update cache update db update db update cache delete cache update db ...
NPM引入元件
原來前端開發的適配也是如此的雜亂無章,感覺比移動端還扯淡,特別是ie8及以下的。在安卓開發中,我們需要經常引入第三方元件,那麼在前端改怎麼引入的?以我們移動端常見的輪播圖為例,首先在github上找到輪播圖的元件 git 上的位址 下面是使用說明 使用說明 獲取 amaze ui swiper 使用...
動態引入元件
在頁面上建立標籤 主要使用的是非同步元件 使用多個時可以使用for迴圈進行建立 通過require.context方法獲取路徑下元件的物件 let requirecomponent require.context components fromitem 在當前目錄下查詢 false,不遍歷子資料夾 ...