iOS 基於MVVM設計模式的微信朋友圈開發

weixin_34413357發表於2018-01-22

分析

前期在敲程式碼之前,需要著重分析一下整個微信朋友圈介面的實現方案,這可能是本篇文章的核心所在了(PS:這裡特別提醒一下廣大開發者,在實現某一個功能前,請務必確定一個實現方案,可能實現的方案千千萬,這就需要開發者通過自身的理解來確定一個最優的方案來實現,而不是一昧毫無頭緒的敲程式碼,造成後期又得重新迭代的悲劇!!!)。微信朋友圈的效果圖如下(PS:萬惡的馬賽克...)。

7877747-7bf621d335a1b0ed.jpg

Moment.jpeg

當然整體的介面的佈局還是比較複雜的,前期看了UI還是挺讓人望而卻步的。首先,我們可以確定的是整體是利用UITableView來實現的,是不是大家已經隱隱約約感受到還是原來的配方,還是熟悉的味道,相同的tableView,變得只是Cell罷了。其次,筆者經過多日在GitHub上搜尋一些實現微信朋友圈的開發的Demo,以及做了大量的市場調查和內容對比,當然這兩個Demo實現微信朋友圈的方法涉及到兩個不同的方案,筆者就帶大家簡單分析一下各自的方案實現過程以及目前存在的弊端(PS:這裡所謂的弊端,只是針對微信朋友圈而言的)。兩者的介面模組劃分如下(PS: ① 紅色框 , ②:綠色框):

7877747-a398236877166891.jpg

Moment_UI.jpeg

當然這兩個Demo的實現朋友圈的 共同之處就是:將圖上所示的紅框①整體用一個UITableViewCell來展示。不同之處 就是:圖上所示的綠框②的控制元件選取不同罷了。

UITableViewCell上佈局子控制元件相對於大家肯定是小菜一碟,這裡筆者就針對兩個Demo在綠框②的控制元件的選取上做文章以及分析其目前存在的弊端。當然這兩個方案目前都不是最最優化的方案,通過分析其中存在弊端,逐漸引申出比較令人合理的方案,當然筆者最終會給出自己的方案,但也許未必是最優的方案,更好的方案或許就存在大家的手中,筆者這裡主要強調的是 知其然,知其所以然。話不多說,Let's Do It!

方案一 【gsdios/GSD_WeiXin】

該方案將綠框②的控制元件選取的是一個普通的UIView,當然內部顯示文字(評論、回覆、點贊)的子控制元件用的是UILabel來展示。雖然這種寫起來比較通俗易懂,就是根據評論列表和點贊列表的內容,不斷修改內部UILabel的frame來達到要求,但是卻帶來了如下的弊端:

佈局複雜:考慮到綠框②內部子控制元件的佈局的複雜性,其作者採用的是其自己寫的SDAutoLayout來實現,筆者對SDAutoLayout用的也不是非常熟練,關於其佈局程式碼的實現請留意其Demo的SDTimeLineCellCommentView.h/m檔案即可,儘管其內部佈局程式碼看起來還算簡單,但是如果我們不使用SDAutoLayout,那麼採用傳統的frame佈局,想想還是比較複雜的,比如:我們要計算出紅框①(UITableViewCell)的高度,首先需要計算出綠框②內部所有子控制元件(UILabel)的尺寸,從而推算出綠框②的整體高度,最終方能確定紅框①(UITableViewCell)的高度。筆者猜想該作者這裡可能主要是為了凸顯SDAutoLayout的自動佈局的強大和便捷,好一個項莊舞劍,意在沛公呀。

動態建立:我們知道紅框①(UITableViewCell)是支援複用的,這是毋庸置疑的,但是我們知道每一條說說(紅框①)中包含的評論列表的個數是不一樣且Cell高度也會不一樣。這樣就會涉及到當使用者滾動朋友圈列表且cell複用的時候,綠框②內部的子控制元件的個數也是動態的,可能增多,又可能減少,這樣就造成了動態增加或刪除綠框②內部的子控制元件,想必大家都知道盡量不要在UITableViewCell中動態建立子控制元件,這是比較耗效能的,常規的做法都是事先在- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(nullable NSString *)reuseIdentifier一口氣建立所有你要顯示的控制元件,這樣你只需要根據資料的屬性來顯示或隱藏某個子控制元件即可,這樣就避免了動態建立子控制元件的場景。但是由於朋友圈列表的每條說說的評論列表個數是不能事先確定的,所以必然會存在綠框②動態建立子控制元件的悲劇。

不支援大資料:對於上面動態建立控制元件的問題,其實該作者在內部SDTimeLineCellCommentView.h/m也是做了優化處理的,其做法主要是將動態建立的子控制元件(UILabel)裝進一個陣列(commentLabelsArray)裡面,這樣可以減少一部分的動態建立子控制元件過程,但是還是會存在動態建立子控制元件,其主要邏輯就是根據你傳進來的說說的評論列表的個數 (commentItemsArray.count)與commentLabelsArray.count比較罷了,如果前者小於等於後者,就不需要動態建立,只是對commentLabelsArray中的子控制元件做顯示和隱藏處理即可;反之如果前者大於後者,這需要動態建立(commentItemsArray.count - commentLabelsArray.count)個子控制元件,然後又被加入到陣列commentLabelsArray裡面的過程。關鍵程式碼如下所示:

首先微信朋友圈的評論列表的個數是支援大資料的(PS:筆者瞎猜的…),那就必須確保綠框②能支援大資料的顯示,顯然隨著評論列表的個數逐漸增多,以及UITableViewCell的不斷複用,則綠框②的commentLabelsArray裡面裝的子控制元件也會越來越多且保持只增不減的趨勢,這樣該方案就顯得比較的無力了。

- (void)setCommentItemsArray:(NSArray *)commentItemsArray{

_commentItemsArray = commentItemsArray;

long originalLabelsCount = self.commentLabelsArray.count;

long needsToAddCount = commentItemsArray.count > originalLabelsCount ? (commentItemsArray.count - originalLabelsCount) : 0;

for (int i = 0; i < needsToAddCount; i++) {

MLLinkLabel *label = [MLLinkLabel new];

[self addSubview:label];

[self.commentLabelsArray addObject:label];

}

}

以上就是【gsdios/GSD_WeiXin】目前筆者發現其存在的些許問題以及談談筆者個人的一些理解。當然這個方案在針對大量的評論資料的處理上或許稍有吃力,但是如果當評論列表的個數是固定,例如:優酷視訊的評論回覆(如下圖)。這個方案也不失為一個好的解決方案。所以說業務場景不同,實現方案不同,可見在敲程式碼之前,先思考後確定實現方案是多麼重要。

7877747-86a2f7d9604bf02f.jpg

YouKu_UI.png

方案二 【zhengwenming/WeChat】

該方案將綠框②的控制元件選取的是一個UITableView,也就是說Cell(紅色框①)裡面巢狀了一個UITableView,其內部子控制元件就是UITableViewCell來處理,後面的處理其實就跟我們平常處理UITableView的方法一樣,建立TableView,遵守協議,實現協議方法… ,可能會不習慣的就是平常建立的TableView,我們都是將其新增在控制器的View上,這裡只是新增在UITableViewCell上罷了,其他並無差異。內部實現說到底其實就是充分利用UITableView的特性,選取不同UITableViewCell來顯示點贊列表和評論列表而已,相比於方案一來說,該方案主要發揮出了UITableView的特性,通過實現UITableView的協議方法就能實現評論和點贊列表的展示,且實現起來更加簡單易懂,這可能是目前市場上絕大多數的做法。雖然外表看似毫無破綻,但是其中隱藏巨大弊端。之前筆者也利用這種方案,寫過類似微信朋友圈的評論回覆,但是其中存在的問題,筆者卻沒有敘述,實屬抱歉,當然這裡筆者將詳述其存在弊端和產生的原因,以及讓大家重新加深對UITableView的理解。弊端如下:

複用問題: 若想保證UITableView滾動流暢,縱享絲滑,就離不開UITableViewCell的複用機制(PS:這個複用機制想必大家應該已經滾瓜爛熟了,這裡筆者就不在贅述),這也是UITableView的核心所在。首先正常情況下,我們可以確定的是紅色框①這個UITableViewCell是能夠Cell複用的,這個應該是毫無爭議的。但是紅色框①內部巢狀的綠色框②這個TableView中,其內部顯示評論資料的UITableViewCell是否也是支援Cell的複用機制呢???可能大家的第一印象就是覺得是能的。但是這裡筆者強調的是 綠色框②中CommentCell是不支援複用的!!!大家認為CommentCell能夠複用的,都是認為其複用機制完全跟紅色框①(MommentCell)的複用機制一樣,都是會隨著使用者滑動的朋友圈列表,MommentCell 和 CommentCell離開都會完全離開螢幕,然後將完全離開螢幕的MommentCell 和 CommentCell存入快取池,等到要顯示Cell的時候又去快取池根據reuseIdentifier去取MommentCell 和 CommentCell,如果取得到,就直接拿來用;如果取不到,就去建立等過程....,這裡筆者只能說cell複用的概念倒是背的的挺熟,但是Cell複用的機制卻不夠理解。原因是:* 之所以紅色框①這個MomentCell能夠遵循Cell複用的機制,是因為首先其所處在的UITableView的尺寸大小是和螢幕尺寸大小一致,其次朋友圈列表能夠滑動的前提就是保證該TableView的內容高度大於TableView的高度,即tableView.contentSize.height > tableView.frame.size.height,需要強調的是:①Cell能否產生複用取決於所處的tableView能否滾動,②並且Cell能夠隨著列表滾動完全離開所處的TableView的顯示範圍。結合這兩點必要條件,很快可以推斷出紅色框①這個UITableViewCell是能夠滿足Cell複用的條件的。接著我們帶著這兩個必要條件來分析一下綠色框②這個TableView,首先明確的是,該TableView的高度是根據評論列表中每個評論內容(CommentCell)的高度總和(PS:tableView.height = cell0.height+cell1.height+cell2.height ...),這樣就導致了該tableView的內容高度等於tableView的尺寸高度,即(tableView.frame.size.height = tableView.contentSize.height),所以評論列表是不會滾動的,這樣就不滿足條件①;其次,其tableView內部的CommentCell相對於所處的tableView的顯示區域是完全暴露的,根本不滿足條件②,所以最終真相大白,水落石出了,是不是豁然開朗,心情舒暢。 當然這裡筆者友情提醒廣大開發者千萬不要誤認為,只要Cell看不見就一定會產生複用的誤區,主要是要明確該Cell相對於所處的TableView的顯示區域是否看不見。(PS:知識點有木有)。最後,如果綠色框②這個TableView一旦失去了Cell的複用機制,用腳趾頭想想也知道,那造成的後果務必會重蹈方案一存在的三個弊端的悲劇,這裡筆者就不再贅述了,且筆者個人認為整體效能還不如方案一的。

方案三 【CoderMikeHe/WeChat】

該方案正是筆者目前使用的方案,該方案不僅很好的解決了方案一和方案二目前存在的弊端,而且使用起來極其簡單方便以及效能優化上更是前兩個方案無法比擬的,當然最主要的還是考察技巧性(黑魔法)。首先筆者在認定該方案之前,前期筆者是做了大量的準備工作,以及仔細琢磨了紅色框①(PS:類似一條說說)這個整體的子模組組成。當然必須明確的是微信朋友圈的需求:綠色框②能夠展示大量的評論資料(即:評論內容列表的個數>=100 ,雖然我們會很少看到某個人的某條說說,有100多個人的評論內容,而且微信的朋友圈資訊流動性非常快,這種大資料的產生會很少發生,但是這種大資料不代表沒有)。①考慮到微信朋友圈這一個硬需求,筆者著重從效能上出發,第一想到的就是利用Cell的複用機制來展示每條說說的評論內容;②考慮到前兩個方案都是把紅色框①當做一個整體來處理,且都來了類似的弊端以及針對評論內容大資料所帶來的效能問題,以免重蹈覆轍,筆者將紅色框①拆分為下圖幾個模組:一條說說(紅色框①) = 組(段)頭(綠色框②) + Cell(紫色框③) + 組(段)尾(黑色框④)。

7877747-c7d9c0ec159e4497.jpg

Moment_Plan3_UI.jpeg

通過上圖所示,雖然該方案在模組劃分上是比較的分散,但是其總體帶來的效能是非常客觀的,大大保證了朋友圈列表滾動的流暢性。其中當然最最主要的原因還是歸功於上圖所示的組(段)頭(綠色框②)、Cell(紫色框③)、組(段)尾(黑色框④)這三個控制元件都是可以通過使用TableView的資料來源方法以及代理方法(程式碼如下)輕鬆實現View的複用機制的,而且都是平常開發中常用的方法,這樣前面兩個方案所存在的弊端就迎刃而解了。

/// UITableViewDelegate

/// 組(段)頭

- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;

/// 組(段)尾

- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;

/// UITableViewDataSource

/// Cell

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

當然,組(段)頭、組(段)尾的內部控制元件佈局,想必對於大家已經是手到擒來的東西,這裡筆者也不過多討論,詳情請參考筆者提供的Demo,自行領會。這裡筆者主要想說的就是Cell(紫色框③),首先平常開發過程中,Cell的寬度一般是跟所處的tableView的寬度是一致的,但是微信朋友圈裡面的這個評論Cell明顯不是,這裡筆者需要強調的是:重寫是個好東西。這裡的關鍵點就是在於重寫自定義的UITableViewCell的- (void)setFrame:(CGRect)frame方法,關鍵程式碼如下:

/// PS:重寫cell的設定尺寸的方法, 這是評論View關鍵

- (void)setFrame:(CGRect)frame{

frame.origin.x = MHMomentContentLeftOrRightInset+MHMomentAvatarWH+MHMomentContentInnerMargin;

frame.size.width = MHMomentCommentViewWidth();

[super setFrame:frame];

}

當然對於這種方案(組(段)頭+Cell+組(段)尾)的實現過程,筆者以前就寫過一篇文章,來詳細介紹這其中的關鍵點。最後,筆者個人認為這個方案目前是實現類似微信朋友圈這種支援無限評論需求的最優雅的實施方案。

當然還有一種方案就是:微信官方團隊做朋友圈開發的實現方案。如果這篇文章能夠有幸被微信的開發人員看到,也請微信的開發人員分享一下微信官方的朋友圈的實現方案哦;或者如果筆者的這個方案正好和微信官方的如出一轍,那麼也請為筆者瘋狂打Call(權威認證)哦。最後筆者希望這篇文章能夠為大家解除些許疑惑,帶來些許幫助。

相關文章