天天看點

TableViewCell的幾種重用方式的差別TableViewCell的幾種重用方式的差別

  • TableViewCell的幾種重用方式的差別
    • 重用機制
      • reuseIdentifier
      • registerNibclassforCellReuseIdentifier
    • 重用生成單元格方法
        • 共同點
        • 不同點
        • 在使用了r方法注冊的情況下
        • 不使用r方法注冊的情況下
    • 最終使用方式
      • 為什麼不用r注冊的方式
      • 尾文

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參數,若真有知道具體用處的,歡迎留言,謝謝.