iOS混合開發庫(GICXMLLayout)五、Texture篇

搬磚的碼農發表於2019-02-27

GICUI元素以及佈局系統都是基於Texture開發的,這裡可能需要做下說明。

. UI元素指的是lablelistimage這些可以直接顯示內容的元素。

. 佈局系統指的是專門用來佈局的皮膚,同時也是一種特殊的UI,你可以理解為一個一種佈局皮膚就是一種UIView,因此也可以設定background-colorheightwidth等屬性。

下面如無特殊說明,UI元素指的是包括佈局系統在內的元素。而所有UI元素都將擁有共同的基礎屬性

在正式介紹GIC是如何基於Texture開發之前,先給各位看一個例子。

iOS混合開發庫(GICXMLLayout)五、Texture篇

假如我們要在專案中做一個類似這樣佈局的TableView,而且還要求其中的圖片寬高比必須是16:9,標題最多顯示兩行,而且還要求適配橫屏和豎屏的切換,對於這樣的需求,我們會怎麼去設計我們的架構,如何去實現這樣的需求?做過類似需求的同學應該深有體會這樣的佈局是多麼的複雜,更不用說一個列表中可能每個cell的佈局方式都不一樣的。而我們在開發這樣的應用的時候就會把大量的時間花費在佈局程式碼上,而且還要考慮效能進而準備各種效能優化措施。

然後看下使用GIC以後你所需要的佈局程式碼。

<!--建立一個頁面-->
<page>
    <!--建立一個tableView,並且將一個包含5個item的陣列作為資料來源-->
    <list data-context=`[1,2,3,4,5]`>
        <!--建立一個section-->
        <section>
            <!--建立for指令,用來根據資料來源迴圈建立list-item,這裡面資料來源包含了5個item,也就是說會建立5個list-item-->
            <for>
                <!--建立list-item,可以理解為UITableViewCell-->
                <list-item separator-inset = "0 0 0 0">
                    <!--建立一個內邊距為12的佈局皮膚-->
                    <inset-panel inset="12">
                        <!--建立一個在垂直方向從上到下自動佈局的皮膚-->
                        <stack-panel>
                            <!--建立標題,並且設定顏色、大小、最多顯示行數等屬性-->
                            <lable text="國際社會積極評價習近平主席在金磚國家工商論壇上的重要講話" lines="2" font-color="191919" font-size="24"/>
                            <!--建立一個宮格佈局的皮膚,每行顯示3個,並且每列之間的間隔為10-->
                            <grid-panel columns="3" column-spacing="10" space-before="10" data-context=`[1,2,3]`>
                                <!--建立一個for指令,根據data-context的陣列內容,自動迴圈建立for包含的元素-->
                                <for>
                                    <!--建立一個比例佈局皮膚,並且將比例設定為16:9-->
                                    <ratio-panel ratio="0.5625">
                                        <!--建立一個iamge元素,用來顯示圖片-->
                                        <image url="http://img5.duitang.com/uploads/item/201204/01/20120401222440_eEjyC.thumb.700_0.jpeg"/>
                                    </ratio-panel>
                                </for>
                            </grid-panel>
                            <!--底部資訊-->
                            <!--建立一個水平方向佈局的皮膚,並且規定每個元素的間隔為10-->
                            <stack-panel is-horizon="1" space="10" space-before="10">
                                <lable text="人民網" font-color="999999"/>
                                <lable text="15評論" font-color="999999"/>
                                <lable text="1小時前" font-color="999999"/>
                            </stack-panel>
                        </stack-panel>
                    </inset-panel>
                </list-item>
            </for>
        </section>
    </list>
</page>
複製程式碼

上面的XML程式碼就能完美的實現上述的需求,你可以數下元素,總共才建立了16個元素,包含了從建立頁面到建立UITableView到最終cell的佈局所有過程。你可以直接將這段XML程式碼寫入一個XML檔案,然後讓GIC載入出來。如果你切換橫豎屏的話還會發現列表內容也會自動切換,並且圖片的比例嚴格按照16:9顯示。這樣的佈局程式碼,在UI設計稿確定並且熟悉各種佈局皮膚的前提下,你只需要幾分鐘就能寫出來。

這樣的開發效率跟傳統的開發方式相比簡直是天壤之別,而且自動繼承Texture的高效能,實際的渲染速度非常快。GICTexture的基礎上進一步的優化了列表的載入速度,使得列表的首屏顯示速度控制在毫秒級,比如在iphone6p上,這樣的佈局首屏顯示只需要大概60毫秒,哪怕是iphone4這樣的手機也能取得90毫秒的優異成績

下面進入正題,介紹下GIC是如何整合Texture的。

由於Texutre在佈局以及渲染上可謂是自成一體,你甚至可以將之理解為另外一套獨立的"UIView"。使用Texutre可以獲取以下兩個優勢:

  1. 優秀的佈局系統。(區別於autolayout)。

    這套佈局系統不僅在效能上比autolayout具有優勢,而且還提供多種不同的強大布局方式,比如:支援FlexBox佈局的ASStackLayoutSpec。而且基於此,你可以自己開發自己的佈局系統。用了Texture以後,你幾乎可以跟frameautolayout說拜拜了。

  2. 非同步渲染能力。

    Texture區別UIView的一大特色功能就是支援background渲染。我們都知道,UIView只能在主執行緒上使用,而我們平常說的介面卡頓大多數情況是由於主執行緒被阻塞引起的,因此我們常規的優化方式就是極力減少主執行緒的壓力。而Texture天生就支援background佈局、渲染,在現在裝置多核CPU的加持下,可以並行渲染、佈局,這極大的提升了我們應用的效能。稍微誇張的說,在你使用Texture的情況下,你幾乎不用再為效能發愁,而平常我們為優化效能準備的各種優化措施在Texture面前將會顯得沒有用武之地。

    注:Texutre能夠支援background渲染的一個技術基礎就是CoreGraphics,因為CoreGraphics是執行緒安全的,因此你可以直接在非UI執行緒下進行CoreGraphics的呼叫。 另外,對於非同步佈局來說,因為Texture的佈局系統本來就是獨立的一套,雖然最終還是會反應在frame上,但是在做佈局計算的時候是獨立於UIView以外的。

Texture的功能肯定不止上面這兩點,但我個人覺得,光是這兩個優勢就能足以讓我決定在實際的專案中使用了。可能有人會說,Texture雖然佈局系統強大,但是使用起來卻並不是很方便,或者說上手難度不容易,對於這點其實我也不否認,但是當你在GIC中直接使用XML來開發介面的時候你就會發現,UI佈局原來也可以這麼舒服(參考上面的XML程式碼)。

GIC在對Texture進行二次封裝的過程中,分為三個部分。

  1. 對佈局系統的封裝
  2. 對ASDisplayNode封裝。
  3. 對ASTableNode以及ASCollectionNode的封裝。

下面一一進行介紹說明

一、對佈局系統的封裝

Texture的佈局類都是繼承自ASLayoutSpec的,從而衍生出各種不同的佈局類。而GIC中的每一種佈局皮膚基本上都是對應了一種ASLayoutSpec,但是GIC在此基礎上做了進一步的封裝,那就是每一種佈局皮膚都繼承自ASDisplayNode,這樣一來每一種佈局皮膚都能單獨設定background-colorwidthheight等屬性,使得每一種佈局皮膚你都可以作為一個單獨的UIView來對待。

之所以這麼做是有原因的,舉個例子。就拿上面那個佈局舉例:

iOS混合開發庫(GICXMLLayout)五、Texture篇

要給整個cell設定一個背景色,然後新增一個圓角。如果佈局皮膚僅僅只是對ASLayoutSpec進行封裝的話,那麼就無法設定背景色和圓角的屬性,因為對於Texture來說,只有ASDisplayNode才能設定這樣的屬性,因此GIC在綜合考慮之下,決定直接每個皮膚都繼承自ASDisplayNode,這樣每一種皮膚都能即提供佈局功能,又能提供ASDisplayNode等各種屬性設定的能力,可謂一舉兩得。

<inset-panel inset="12" background-color="00000033" corner-radius="10">
複製程式碼

這樣,你在寫佈局程式碼的同時又能給佈局皮膚設定各種屬性。

GIC整個的佈局系統的類圖大概如下:

佈局系統的類圖

目前GIC共提供了7中佈局皮膚,每一種皮膚都對應一種ASLayoutSpec,每一種佈局皮膚的使用方法可以從文件上檢視

對於每一種佈局皮膚來說,所要做的事情就是在layoutSpecThatFits方法下面返回正確的ASLayoutSpec即可。比如:GICPanel的程式碼。

@implementation GICPanel
+(NSString *)gic_elementName{
    return @"panel";
}
-(id)init{
    self = [super init];
    self.automaticallyManagesSubnodes = YES;
    return self;
}
-(ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize{
    ASAbsoluteLayoutSpec *absoluteSpec = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:self.gic_displayNodes];
    return absoluteSpec;
}
@end
複製程式碼

從程式碼上來說,還是比較簡單的。

而另外一個值得一提的是GICGridPanel這個皮膚是GIC自定義的一個皮膚,而非Texture提供的。有興趣的可以看下原始碼

二、對ASDisplayNode封裝

GIC中的UI元素都是直接或者間接繼承自ASDisplayNode,Texture本身提供了多種基本UI元素,比如:ASTextNodeASNetworkImageNodeASScrollNodeASTableNode等等,而對這些基礎Node的組合應用基本能夠應付大多數需求,如果這些基礎Node無法提供你所需的功能,您也能通過自定義的方式建立符合你需求的Node,在GIC中,佈局皮膚就是一個很好的例子。

GICASDisplayNode的封裝,其實就是對各種基礎Node的二次封裝的過程,拿image元素舉例來說,image其實就是繼承自ASNetworkImageNode的自定義元素(原始碼),只不過就是增加了屬性而已,而其他的UI元素也是這麼做的,在前面一篇部落格中已經提到,這些UI元素都算做是GIC自定義元素的範疇。

三、對ASTableNode以及ASCollectionNode的封裝。

GIC中的list元素其實也是一個對ASTableNode二次封裝的自定義元素。之所以這裡要單獨提出來說,那是因為GICASTableNode的基礎上做了進一步的優化。

基本的封裝流程其實跟平常的UITableView的開發流程差不多,從HeaderFooterSectionUITableViewCell等等UITableView的特性一樣不落,所有的list-item(UITableViewCell)必須被包含在section中,一個list至少需要一個section,每個section可以包含獨立的headerfooter,在使用XML寫list的時候你會發現開發流程跟UITableView的開發流程差不多,只是你現在只需要使用XML就能實現UITableView的功能。

基本的封裝相對來說還是比較簡單的,但是list花了很大的精力在另外一個優化上,那就是一次性大量資料載入的優化上。

舉一個例子,一個列表需要一次性載入100條資料,也就是說需要建立100個Cell,而且那些Cell的佈局還是比較複雜的(先不要問“為什麼需要一次性載入100條資料,不會分批載入嗎?”)。如果不加優化一股腦的直接全部插入ASTableNode,那麼你會發現要經過好幾秒才能顯示出來,這並不是說ASTableNode效能不行才需要耗費如此長時間,ASTableNode已經啟用了非同步載入,但是要建立100個Cell也是要消耗很大的CPU資源的,雖然是在非UI執行緒上建立的,但是需要消耗的CPU資源是跑不掉的,再說了,最終能夠顯示出來的檢視還必須是UIView,哪怕在極端情況下,一次性在UI執行緒建立100個UIView也是能讓UI執行緒夠喝一壺的了!因此這並不是ASTableNode的鍋,這鍋應該開發者背,不管是用UITableView還是ASTableNode,一次性載入100多條資料甚至更多,本來就是有問題的。哪怕實際的需求確實是要求一次性載入100條,但是我們難道在實現的時候不能人為分批載入嗎?

GIC正是基於這樣的思路提供了一個通用的解決方案,直接將此功能封裝到了listcollection-view兩個元素中。

下面主要著重分享下這個解決方案。

首先是如何分批載入,因為當設定資料來源的時候,肯定是一次性來100條資料的,那麼如何將這100條資料分成10批載入(假設每次載入10條資料),要是有一個方法能夠自動將100條資料按照每次處理10條資料方式來處理所有資料就好了,然而實際的情況可能更復雜,也許當你100條資料還未全部載入完畢,又插入了一批新的資料,又因為GIC是支援非同步載入的,也即是意味著新插入的資料是可以在任何執行緒上插入的,而且插入的資料有可能並不是一次性加入而是有可能是一條一條插入的(事實上,GIC中的for指令在插入資料的時候就是一條一條插入的),這又極大的增加了開發難度。這時候就想著,有沒有什麼方法可以使得在特定的時間內將所有插入的資料都先放到一邊,然後按照每次處理10條資料的方式來處理資料,這時候想到了類似RXJavaRXJS中的buffer方法,而RAC只提供了bufferWithTime的方法,因此GIC就需要在這個方法上做進一步的處理。

bufferWithTime的作用就是在給定的時間內所有被髮射的資料都會被打包到一個陣列中,等時間到了再一次性的將整個陣列發射出來。如下圖:

iOS混合開發庫(GICXMLLayout)五、Texture篇

GIC利用了這個方法的特性,先將0.05秒內的所有的資料全部打包到一起,再一次性發射出來,這樣就確保了0.05秒內的所有資料都能在超時後得到處理,並且統一排程到UI執行緒進行插入操作。也就是說在資料來源層面,你儘管往資料來源中新增資料,然後由bufferTime再統一新增到一個單獨的陣列中,然後等待(0.05秒)超時後再一次性的在UI執行緒上發射出來。然後進入下一個處理邏輯。

首先是判斷當前ASTableNode是否處於處理資料狀態(可以通過ASTableNodeisProcessingUpdates屬性來判斷),如果處於Processing狀態,那麼將這批資料再重新放回bufferTime中,等待下一次的超時處理。而如果當前ASTableNode並不是處於Processing狀態,那麼就將這批資料按照每批10條資料的方式分批處理(這裡用到了遞迴方式來處理),當每次10條資料處理完畢後再遞迴處理下一個10條資料,直到所有的資料都處理完成。

這樣的處理方式不僅保證了在載入大量資料的時候能夠確保準確插入,也保證了首屏資料的顯示速度。事實上,按照每次處理10條資料的邏輯來看,也就是說首屏顯示出來的資料是10條資料,這樣不管一次性載入多少資料,首屏的10條資料都會在短時間內載入完畢並且顯示出來,這樣在使用者體驗上將會得到一個極大的提升,哪怕這時候後臺其實還在處理剩餘的資料,但是對於使用者來說這個過程是無感知的。使用者只會看到資料很快就顯示出來了。

其實這樣的處理方式並不是專門為ASTableNode提供的,任何我們平常開發UITableView的時候都可以使用這樣的解決方案來提高首屏顯示速度。而在實際的測試結果來看,從資料載入到首屏顯示(10條資料)的時間差不多是60毫秒(iphone6),這個時間包含了XML解析資料繫結自動佈局計算渲染顯示這一整個流程,可以說是非常的快速了。即是是在iphone4那樣的裝置上,也能在90毫秒內完成。(這裡的時間測試結果,佈局參照物件為開篇提到的那個設計)。

我試過直接使用常規的開發方式直接使用UITableView來開發,同樣是顯示10條資料,從拿到資料到最終顯示完畢,在iphone6上差不多是200毫秒。當然這裡面並未經過太多的效能優化,也許經過優化過後能夠爭取在150毫秒甚至更短的時間內完成,但是這樣的比較意義在於,當你使用GIC後,你壓根就無需去考慮效能優化的問題,並且無需進行復雜的架構設計,同樣能夠在極短的時間內將內容顯示出來。

注:如何測試UITableView從拿到資料到顯示的時間?

CGFloat *d1 = [[NSDate date] timeIntervalSince1970];
// 處理資料,比如計算cell的高度
dispatch_async(dispatch_get_main_queue(), ^{
    [self.tableView reloadData];
    dispatch_async(dispatch_get_main_queue(), ^{
        CGFloat *d2 = [[NSDate date] timeIntervalSince1970];
        // d2-d1  就能得到從拿到資料到顯示完畢的時間差
    });
});
複製程式碼

最後

GIC得益於Texture的高效能、強大的佈局系統,使得在使用XML寫傳統的UI佈局的時候不僅在開發效率上得到極大的提高,而且也直接提升了效能。哪怕您僅僅是拿GIC作為一種UI佈局工具,也能極大的提升你的開發效率,而且GIC支援區域性UI替換,也就是說你一個頁面中部分UI使用XML來佈局,部分UI還是使用原來的coding方式來佈局,可以說非常的靈活。

更進一步,當你將業務邏輯使用JavaScript來寫的時候,那麼意味著整個APP具備了熱更新的能力。

相關文章