重用機(jī)制
關(guān)于TableView的重用機(jī)制相信網(wǎng)上教程一堆,這里不作過(guò)多說(shuō)明,但是有幾個(gè)重點(diǎn)會(huì)說(shuō)明下:
reuseIdentifier
個(gè)人覺(jué)得最重要的一點(diǎn),就是Cell的reuseIdentifier屬性,其實(shí)不管你是注冊(cè)nib還是class,最終的重用靠的主要是這個(gè)標(biāo)識(shí)符,只有這個(gè)屬性有值的Cell,在滑動(dòng)出TableView后才會(huì)進(jìn)入重用隊(duì)列,隊(duì)列有Cell,你在dequeueReusable的時(shí)候才能拿到Cell,如果你沒(méi)注冊(cè),但是只要Cell有這個(gè)屬性值,那么它再滑出去后也必然能被重用
registerNib(class):forCellReuseIdentifier
上面說(shuō)的注冊(cè)單元格的兩種方式,簡(jiǎn)稱(chēng)r,他們將Cell以nib或者class的形式注冊(cè),但是重點(diǎn)還是后面的forCellReuseIdentifier,也即是說(shuō)這個(gè)Cell,會(huì)因?yàn)檫@個(gè)參數(shù)而賦值給reuseIdentifier屬性,所以這個(gè)Cell才能被重用.
總結(jié):重點(diǎn)還是reuseIdentifier,不管你注冊(cè)沒(méi)注冊(cè),只要你產(chǎn)生的Cell的屬性reuseIdentifier是有值的,那么它滑動(dòng)出TableView后就可以被再次重用.那registerNib(class):是干嘛用的呢,后面會(huì)再提到.
重用生成單元格方法
關(guān)于重用生成TableViewCell的機(jī)制這里不作多的說(shuō)明,這里只是對(duì)比下幾種重用方式的區(qū)別,目前重用單元格使用下面兩種方法:
- dequeueReusableCellWithIdentifier:
- dequeueReusableCellWithIdentifier: forIndexPath:
這里暫且簡(jiǎn)稱(chēng)上面前者為方法d1,后者為d2.
這兩個(gè)方法的返回值都是UITableviewCell,網(wǎng)上大部分的講解或者抄襲的講解可能說(shuō)的區(qū)別僅僅是d1可能返回nil,而d2則在沒(méi)有使用r方法的情況下會(huì)崩潰,這些都沒(méi)問(wèn)題,不然也不會(huì)有那么多抄襲的博客了.這里先說(shuō)下他們的共同點(diǎn)和不同點(diǎn):
共同點(diǎn)
他們都會(huì)先根據(jù)identifier來(lái)復(fù)用在重用隊(duì)列的單元格,且復(fù)用不到的時(shí)候會(huì)去根據(jù)r方法注冊(cè)的Cell來(lái)獲取新的,r方法注冊(cè)的時(shí)候已經(jīng)給cell了identifier,因此這兩個(gè)方法新生成的cell,都會(huì)有重用標(biāo)示,都可以進(jìn)入重用隊(duì)列(前提是使用r方法注冊(cè)過(guò)),不會(huì)為nil.
不同點(diǎn)
如果你沒(méi)有使用r方法注冊(cè),在找r方法產(chǎn)生的cell的時(shí)候,區(qū)別就來(lái)了,因?yàn)闆](méi)有用r注冊(cè),d1會(huì)返回nil,而d2則直接崩潰.
說(shuō)的再細(xì)點(diǎn):
在使用了r方法注冊(cè)的情況下
只要使用r方法注冊(cè)了單元格設(shè)置了標(biāo)識(shí)符(前提是注冊(cè)的沒(méi)問(wèn)題),那么在重用的時(shí)候,不管你是用d1還是d2,只要重用不到,就返回r方法注冊(cè)的帶標(biāo)識(shí)符的新單元格(新生成,非重用),即使你用d1來(lái)獲取cell,也不會(huì)返回給你nil,這兩種情況目測(cè)沒(méi)發(fā)現(xiàn)任何區(qū)別,那么問(wèn)題來(lái)了,d2方法的IndexPath參數(shù),系統(tǒng)是干嘛了?好吧,其實(shí)我也不知道,官方文檔是這么說(shuō)的:
This method uses the index path to perform additional configuration based on the cell’s position in the table view
所以,文檔也說(shuō)的不清不楚,只是說(shuō)會(huì)根據(jù)位置來(lái)增加一些額外的配置信息,具體是啥信息,不知道,我查了下Stack Overflow,這個(gè)參數(shù),老外們也是諸多疑問(wèn),如果有哪位知道具體的區(qū)別,請(qǐng)告知,謝謝.在使用r方法注冊(cè)的前提下,兩者目測(cè)并沒(méi)有什么區(qū)別.既然目測(cè)沒(méi)有什么區(qū)別,那自然使用d2比較合適,因?yàn)楫吘顾潜容^新的方法.
另外這里說(shuō)下一種寫(xiě)法,雖然也沒(méi)什么問(wèn)題,但是我個(gè)人覺(jué)得不提倡:先使用d1來(lái)獲取單元格,然后判斷是否為空,為空的話(huà)再去注冊(cè)并調(diào)用d1.說(shuō)直白點(diǎn)就是,先不注冊(cè),先去用d1重用,為空的話(huà)就使用r注冊(cè),然后再用d1,就有了Cell了.但是其實(shí)只要只要使用了r方法且注冊(cè)的是正確的,不管你是用d1和d2都不會(huì)是空,既然這樣,那為何不直接在TableView對(duì)象生成完了就先使用r注冊(cè)下,cellforRow直接使用d1就完了,它肯定不會(huì)是nil的,用上面這種寫(xiě)法其實(shí)if(!cell)里只走一次后就不會(huì)再走了.
好吧,其實(shí)說(shuō)了這么多,無(wú)非想說(shuō)明一點(diǎn):
只要使用了r方法且注冊(cè)的nib或者class沒(méi)問(wèn)題,那么不管你是用d1還是d2,它始終不會(huì)是空,都會(huì)正常生成單元格.
結(jié)論:在使用r方法注冊(cè)過(guò)的前提下,使用d2方法來(lái)重用單元格,不要使用d1(雖然d1也沒(méi)問(wèn)題,但是畢竟d2比較新)
不使用r方法注冊(cè)的情況下
d1和d2的返回值都是UITableviewCell,前面已經(jīng)說(shuō)了,在這種情況下,d1返回可能是nil,而d2會(huì)崩潰.所以不使用r方法的情況下,我們直接排除掉d2,不使用d2來(lái)重用單元格,使用d1.
d1在返回的是nil的情況下該如何做呢,再重新注冊(cè)?No,No,No,上面已經(jīng)說(shuō)過(guò)了,既然你要注冊(cè),就沒(méi)必要做判斷,提前注冊(cè)好這里直接就不會(huì)是nil,而且我這里已經(jīng)寫(xiě)到不使用r方法注冊(cè)的情況下了,不要讓我再繞回去了.說(shuō)白了,你只要想用r方法,那么就乖乖用d2就完了.
r方法其實(shí)它的重點(diǎn)在于你用d方法拿不到單元格的時(shí)候,d1或者d2方法會(huì)根據(jù)r方法注冊(cè)的nib或者class,生成新的單元格,并賦值identifier,那么這里其實(shí)也是一樣的,在Cell為nil的時(shí)候,你只需要生成對(duì)應(yīng)的單元格,且給identifier賦值即可.常規(guī)單元格使用方法:initWithStyle: reuseIdentifier:
,它可以指定identifier,即生成的單元格是可以正常重用的.
大部分的時(shí)候我們需要使用xib來(lái)創(chuàng)建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.剛說(shuō)了,新生成的Cell要能重用,重點(diǎn)是identifier有值,然后這兩種方式都沒(méi)有給identifier賦值的參數(shù),有好多人可能直接就這么用了,這樣其實(shí)根本就沒(méi)重用,每次進(jìn)cellforRow,使用d1方法都會(huì)得到nil,每次都會(huì)生成新的,浪費(fèi)內(nèi)存.那上面這兩種方法拿到的cell要怎么設(shè)置它的identifier呢,很簡(jiǎn)單,需要直接在xib的cell里設(shè)置,打開(kāi)cell的第四個(gè)選項(xiàng)卡你就可以看到Identifier了,或者還有個(gè)辦法,在x1或者x2最后,直接[cell setValue:@"xx" forKey:@"reuseIdentifier"];
這樣它在移出TableView外后就可以進(jìn)入到重用隊(duì)列了.
注意:這一步非常重要,不設(shè)置標(biāo)識(shí)符的話(huà),重用沒(méi)有任何意義,每次進(jìn)來(lái)都會(huì)是nil,都會(huì)重新生成!
最終使用方式
大部分情況下,我們都是使用xib來(lái)創(chuàng)建cell,根據(jù)上面的內(nèi)容綜合,大概可以總結(jié)出下面兩種方式來(lái)重用單元格
- r+d2
使用r方法先注冊(cè),然后再使用d2重用單元格,不用判斷cell是否為nil
- d1+x1 or d1+x2(xib里或者代碼設(shè)置identifier)
不使用r方法注冊(cè),使用d1方法重用單元格,但需要判斷是否為空,為空則使用x1或者x2方法生產(chǎn)新的cell并設(shè)置Identifier.
很明顯,第一種方式更簡(jiǎn)潔,首先在TableView初始化完后,注冊(cè),然后直接重用即可.常規(guī)來(lái)說(shuō)只需要xib上做好UI即可,如果你每個(gè)界面每個(gè)TableView都是單獨(dú)寫(xiě)的,且cell都是一個(gè)一個(gè)單獨(dú)寫(xiě)的,那么這么寫(xiě)會(huì)好點(diǎn).
但是我更傾向于第二種,以下是原因:
為什么不用r注冊(cè)的方式:
假設(shè)的app是在賣(mài)某種產(chǎn)品,有兩種類(lèi)型,數(shù)據(jù)都是一個(gè)接口給的.這兩種產(chǎn)品一種可以隨意投資,算是常規(guī)產(chǎn)品,一種則有上限,它比常規(guī)產(chǎn)品多一個(gè)百分比的字段.UI上則只是這個(gè)特殊產(chǎn)品有個(gè)百分比的進(jìn)度條,常規(guī)產(chǎn)品則沒(méi)有進(jìn)度條.
這里姑且不討論數(shù)據(jù)結(jié)構(gòu)問(wèn)題,很多剛培訓(xùn)出來(lái)的同學(xué)可能會(huì)這么做:直接新建兩套Cell類(lèi)文件(兩套h和m)并關(guān)聯(lián)xib,連線(xiàn),然后r方法注冊(cè),d2方法重用,再low一點(diǎn)的,d1方法重用,判斷為空的話(huà)再r方法注冊(cè)再d1方法重用.
既然都是同樣的產(chǎn)品,只是類(lèi)型不同,為何就不是一個(gè)類(lèi),哪怕繼承與同一個(gè)父類(lèi)也行,為什么要單獨(dú)分開(kāi)?有人可能會(huì)說(shuō),xib得要倆啊?是,新建一個(gè)類(lèi)只能有一個(gè)xib,但是誰(shuí)規(guī)定了一個(gè)xib不能有多個(gè)cell或者視圖.如果不能的話(huà),為什么可以拖多個(gè)cell甚至別的UIView到同一個(gè)xib里?蘋(píng)果是吃飽了撐著做這個(gè)功能的?
個(gè)人建議:相同類(lèi)型的UI,只建一套h.m文件,UI則放在一個(gè)xib里即可,個(gè)人覺(jué)得大部分情況下,一個(gè)TableView的cell都可以在一套h.m文件內(nèi),當(dāng)然,情況比較復(fù)雜的話(huà)另當(dāng)別論,具體的結(jié)構(gòu)需要根據(jù)實(shí)際情況而定,像上面這種情況,只需要建一套Cell類(lèi)文件(文件默認(rèn)包含一個(gè)類(lèi))即可,作為常規(guī)產(chǎn)品,特殊產(chǎn)品你可以繼承與它,但不要單獨(dú)寫(xiě)h.m文件,直接就在原來(lái)的文件中寫(xiě)即可,一套h.m文件一個(gè)類(lèi),一個(gè)xib一個(gè)視圖,那是剛培訓(xùn)出來(lái)的做法,你見(jiàn)過(guò)蘋(píng)果官方的api每個(gè)類(lèi)都是單獨(dú)放在一個(gè)文件里?如果你的產(chǎn)品有n個(gè),那文件不是得多的要死?
那如何使用呢?首先,這種情況,r方法肯定是不能用了,為什么?因?yàn)樗鼤?huì)報(bào)錯(cuò),r方法(使用nib)的時(shí)候,會(huì)根據(jù)xib的元素注冊(cè)成單元格,但是前提是它能找到,如果一個(gè)xib建立了多個(gè)cell,r方法就會(huì)報(bào)錯(cuò),它不知道你要用哪個(gè),那你如果單純是為了用r+d2方法而用,行,就建n個(gè)xib文件慢慢玩吧
所以,這種情況,使用d1+x方法即可,x方法最后cell的獲取是從數(shù)組中得到的,而這個(gè)數(shù)組其實(shí)就是xib的所有文件,之前寫(xiě)的是數(shù)組的firstObject,現(xiàn)在按照下標(biāo)讀取即可.
所以如果是常規(guī)就一種產(chǎn)品的cell就那么一個(gè)UI,那么ok,可以直接r+d2,如果一種產(chǎn)品類(lèi)型很多,則使用d1+x.
那為什么我還是選擇后者,這里涉及到UITableview的封裝.具體這里不多說(shuō),大概就是為了保證UITableview的效率,具體可以搜下為什么要封裝UITableview.封裝后,cellForRow會(huì)集中在一個(gè)地方,外部則需要告訴它諸如行高,重用標(biāo)識(shí),下標(biāo)等,因?yàn)榉庋b,所以要適合所有的可能性,所以這里必然不能使用r+d2的方法了,只能使用d1+x方法了.
尾文:
目前正在重寫(xiě)app架構(gòu),UITableView也在封裝,關(guān)于重用這塊剛好有感而發(fā),所以寫(xiě)了這么點(diǎn)東西,文中提到的d2方法中的indexpath參數(shù),若真有知道具體用處的,歡迎留言,謝謝.