TableViewCell的幾種重用方式的區別

TableViewCell的幾種重用方式的區別

重用機制

關於TableView的重用機制相信網上教程一堆,這裏不作過多說明,但是有幾個重點會說明下:

reuseIdentifier

個人覺得最重要的一點,就是Cell的reuseIdentifier屬性,其實不管你是註冊nib還是class,最終的重用靠的主要是這個標識符,只有這個屬性有值的Cell,在滑動出TableView後纔會進入重用隊列,隊列有Cell,你在dequeueReusable的時候才能拿到Cell,如果你沒註冊,但是隻要Cell有這個屬性值,那麼它再滑出去後也必然能被重用

registerNib(class):forCellReuseIdentifier

上面說的註冊單元格的兩種方式,簡稱r,他們將Cell以nib或者class的形式註冊,但是重點還是後面的forCellReuseIdentifier,也即是說這個Cell,會因爲這個參數而賦值給reuseIdentifier屬性,所以這個Cell才能被重用.

總結:重點還是reuseIdentifier,不管你註冊沒註冊,只要你產生的Cell的屬性reuseIdentifier是有值的,那麼它滑動出TableView後就可以被再次重用.那registerNib(class):是幹嘛用的呢,後面會再提到.

重用生成單元格方法

關於重用生成TableViewCell的機制這裏不作多的說明,這裏只是對比下幾種重用方式的區別,目前重用單元格使用下面兩種方法:

  • dequeueReusableCellWithIdentifier:
  • dequeueReusableCellWithIdentifier: forIndexPath:

這裏暫且簡稱上面前者爲方法d1,後者爲d2.
這兩個方法的返回值都是UITableviewCell,網上大部分的講解或者抄襲的講解可能說的區別僅僅是d1可能返回nil,而d2則在沒有使用r方法的情況下會崩潰,這些都沒問題,不然也不會有那麼多抄襲的博客了.這裏先說下他們的共同點和不同點:

共同點

他們都會先根據identifier來複用在重用隊列的單元格,且複用不到的時候會去根據r方法註冊的Cell來獲取新的,r方法註冊的時候已經給cell了identifier,因此這兩個方法新生成的cell,都會有重用標示,都可以進入重用隊列(前提是使用r方法註冊過),不會爲nil.

不同點

如果你沒有使用r方法註冊,在找r方法產生的cell的時候,區別就來了,因爲沒有用r註冊,d1會返回nil,而d2則直接崩潰.

說的再細點:

在使用了r方法註冊的情況下

只要使用r方法註冊了單元格設置了標識符(前提是註冊的沒問題),那麼在重用的時候,不管你是用d1還是d2,只要重用不到,就返回r方法註冊的帶標識符的新單元格(新生成,非重用),即使你用d1來獲取cell,也不會返回給你nil,這兩種情況目測沒發現任何區別,那麼問題來了,d2方法的IndexPath參數,系統是幹嘛了?好吧,其實我也不知道,官方文檔是這麼說的:

This method uses the index path to perform additional configuration based on the cell’s position in the table view

所以,文檔也說的不清不楚,只是說會根據位置來增加一些額外的配置信息,具體是啥信息,不知道,我查了下Stack Overflow,這個參數,老外們也是諸多疑問,如果有哪位知道具體的區別,請告知,謝謝.在使用r方法註冊的前提下,兩者目測並沒有什麼區別.既然目測沒有什麼區別,那自然使用d2比較合適,因爲畢竟它是比較新的方法.

另外這裏說下一種寫法,雖然也沒什麼問題,但是我個人覺得不提倡:先使用d1來獲取單元格,然後判斷是否爲空,爲空的話再去註冊並調用d1.說直白點就是,先不註冊,先去用d1重用,爲空的話就使用r註冊,然後再用d1,就有了Cell了.但是其實只要只要使用了r方法且註冊的是正確的,不管你是用d1和d2都不會是空,既然這樣,那爲何不直接在TableView對象生成完了就先使用r註冊下,cellforRow直接使用d1就完了,它肯定不會是nil的,用上面這種寫法其實if(!cell)裏只走一次後就不會再走了.
好吧,其實說了這麼多,無非想說明一點:
只要使用了r方法且註冊的nib或者class沒問題,那麼不管你是用d1還是d2,它始終不會是空,都會正常生成單元格.

結論:在使用r方法註冊過的前提下,使用d2方法來重用單元格,不要使用d1(雖然d1也沒問題,但是畢竟d2比較新)

不使用r方法註冊的情況下

d1和d2的返回值都是UITableviewCell,前面已經說了,在這種情況下,d1返回可能是nil,而d2會崩潰.所以不使用r方法的情況下,我們直接排除掉d2,不使用d2來重用單元格,使用d1.
d1在返回的是nil的情況下該如何做呢,再重新註冊?No,No,No,上面已經說過了,既然你要註冊,就沒必要做判斷,提前註冊好這裏直接就不會是nil,而且我這裏已經寫到不使用r方法註冊的情況下了,不要讓我再繞回去了.說白了,你只要想用r方法,那麼就乖乖用d2就完了.

r方法其實它的重點在於你用d方法拿不到單元格的時候,d1或者d2方法會根據r方法註冊的nib或者class,生成新的單元格,並賦值identifier,那麼這裏其實也是一樣的,在Cell爲nil的時候,你只需要生成對應的單元格,且給identifier賦值即可.常規單元格使用方法:initWithStyle: reuseIdentifier:,它可以指定identifier,即生成的單元格是可以正常重用的.
大部分的時候我們需要使用xib來創建cell,那麼可能的用法有下面幾種:

if (!cell) {
    UINib *nib = [UINib nibWithNibName:cellId bundle:nil];
    cell = [nib instantiateWithOwner:nil options:nil].firstObject;
//        NSArray *files = [[NSBundle mainBundle] loadNibNamed:@"xx" owner:nil options:nil];
//        cell = files.firstObject;
    CLog(@"生成了新的單元格");
}

或者:

if (!cell) {
//        UINib *nib = [UINib nibWithNibName:cellId bundle:nil];
//        cell = [nib instantiateWithOwner:nil options:nil].firstObject;
    NSArray *files = [[NSBundle mainBundle] loadNibNamed:@"xx" owner:nil options:nil];
    cell = files.firstObject;
    CLog(@"生成了新的單元格");
}

上面兩種方式姑且叫x1和x2.剛說了,新生成的Cell要能重用,重點是identifier有值,然後這兩種方式都沒有給identifier賦值的參數,有好多人可能直接就這麼用了,這樣其實根本就沒重用,每次進cellforrow,使用d1方法都會得到nil,每次都會生成新的,浪費內存.那上面這兩種方法拿到的cell要怎麼設置它的identifier呢,很簡單,需要直接在xib的cell裏設置,打開cell的第四個選項卡你就可以看到Identifier了,或者還有個辦法,在x1或者x2最後,直接[cell setValue:@"xx" forKey:@"reuseIdentifier"];

這樣它在移出TableView外後就可以進入到重用隊列了.

注意:這一步非常重要,不設置標識符的話,重用沒有任何意義,每次進來都會是nil,都會重新生成!

最終使用方式

大部分情況下,我們都是使用xib來創建cell,根據上面的內容綜合,大概可以總結出下面兩種方式來重用單元格

  • r+d2

使用r方法先註冊,然後再使用d2重用單元格,不用判斷cell是否爲nil

  • d1+x1 or d1+x2(xib或者代碼設置identifier)

不使用r方法註冊,使用d1方法重用單元格,但需要判斷是否爲空,爲空則使用x1或者x2方法生產新的cell並設置Identifier.

很明顯,第一種方式更簡潔,首先在TableView初始化完後,註冊,然後直接重用即可.常規來說只需要xib上做好UI即可,如果你每個界面每個TableView都是單獨寫的,且cell都是一個一個單獨寫的,那麼這麼寫會好點.
但是我更傾向於第二種,以下是原因:

爲什麼不用r註冊的方式:

假設的app是在賣某種產品,有兩種類型,數據都是一個接口給的.這兩種產品一種可以隨意投資,算是常規產品,一種則有上限,它比常規產品多一個百分比的字段.UI上則只是這個特殊產品有個百分比的進度條,常規產品則沒有進度條.
這裏姑且不討論數據結構問題,很多剛培訓出來的同學可能會這麼做:直接新建兩套Cell類文件(兩套h和m)並關聯xib,連線,然後r方法註冊,d2方法重用,再low一點的,d1方法重用,判斷爲空的話再r方法註冊再d1方法重用.

既然都是同樣的產品,只是類型不同,爲何就不是一個類,哪怕繼承與同一個父類也行,爲什麼要單獨分開?有人可能會說,xib得要倆啊?是,新建一個類只能有一個xib,但是誰規定了一個xib不能有多個cell或者視圖.如果不能的話,爲什麼可以拖多個cell甚至別的UIView到同一個xib裏?蘋果是吃飽了撐着做這個功能的?

個人建議:相同類型的UI,只建一套h.m文件,UI則放在一個xib裏即可,個人覺得大部分情況下,一個TableView的cell都可以在一套h.m文件內,當然,情況比較複雜的話另當別論,具體的結構需要根據實際情況而定,像上面這種情況,只需要建一套Cell類文件(文件默認包含一個類)即可,作爲常規產品,特殊產品你可以繼承與它,但不要單獨寫h.m文件,直接就在原來的文件中寫即可,一套h.m文件一個類,一個xib一個視圖,那是剛培訓出來的做法,你見過蘋果官方的api每個類都是單獨放在一個文件裏?如果你的產品有n個,那文件不是得多的要死?

那如何使用呢?首先,這種情況,r方法肯定是不能用了,爲什麼?因爲它會報錯,r方法(使用nib)的時候,會根據xib的元素註冊成單元格,但是前提是它能找到,如果一個xib建立了多個cell,r方法就會報錯,它不知道你要用哪個,那你如果單純是爲了用r+d2方法而用,行,就建n個xib文件慢慢玩吧

所以,這種情況,使用d1+x方法即可,x方法最後cell的獲取是從數組中得到的,而這個數組其實就是xib的所有文件,之前寫的是數組的firstObject,現在按照下標讀取即可.

所以如果是常規就一種產品的cell就那麼一個UI,那麼ok,可以直接r+d2,如果一種產品類型很多,則使用d1+x.

那爲什麼我還是選擇後者,這裏涉及到UITableview的封裝.具體這裏不多說,大概就是爲了保證UITableview的效率,具體可以搜下爲什麼要封裝UITableview.封裝後,cellforrow會集中在一個地方,外部則需要告訴它諸如行高,重用標識,下標等,因爲封裝,所以要適合所有的可能性,所以這裏必然不能使用r+d2的方法了,只能使用d1+x方法了.

尾文:

目前正在重寫app架構,TableView也在封裝,關於重用這塊剛好有感而發,所以寫了這麼點東西,文中提到的d2方法中的indexpath參數,若真有知道具體用處的,歡迎留言,謝謝.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章