前言
UITableview在iOS中的使用頻率是非常高的.通常,我們只需要通過(guò)設(shè)置代理,并且在代理方法tableView:cellForRowAtIndexPath: 調(diào)用dequeueReusableCellWithIdentifier:獲取cell并直接使用.但是從沒(méi)有細(xì)致得想過(guò)其中的過(guò)程與機(jī)制,并且知道最近面試(此次面試的問(wèn)題)的時(shí)候,才發(fā)現(xiàn)自己表述得不太好.這種感覺就好像你每天都準(zhǔn)時(shí)吃飯(滑稽),突然有一天有人問(wèn)你人為什么吃飯的時(shí)候自己卻沒(méi)能及時(shí)答上來(lái)的那種感覺.說(shuō)明自己學(xué)得不夠扎實(shí).as we know,
如果你不能將知識(shí)通過(guò)簡(jiǎn)潔的語(yǔ)言表達(dá)出來(lái),那說(shuō)明你還沒(méi)掌握這個(gè)知識(shí).
借此機(jī)會(huì),將這個(gè)知識(shí)點(diǎn)記錄下來(lái)
reuseIdentifier
對(duì)于reuseIdentifier,官方文檔是這樣解釋的:
The reuse identifier is associated with a
UITableViewCellobject that the table-view’s delegate creates with the intent to reuse it as the basis (for performance reasons) for multiple rows of a table view. It is assigned to the cell object ininitWithFrame:reuseIdentifier:and cannot be changed thereafter.
AUITableViewobject maintains a queue (or list) of the currently reusable cells, each with its own reuse identifier, and makes them available to the delegate in thedequeueReusableCellWithIdentifier:method.
冒死翻譯一下:
這個(gè)復(fù)用標(biāo)識(shí)關(guān)聯(lián)UITableviewCell對(duì)象,在tableview代理中創(chuàng)建帶有”標(biāo)識(shí)符”來(lái)復(fù)用cell對(duì)象,而且作為tableview多行顯示的原型(性能的原因).通過(guò)initWithFrame:reuseIdentifier:來(lái)指定一個(gè)cell對(duì)象而且在調(diào)用這個(gè)方法之后就不能修改了.在UITableView對(duì)象維護(hù)一個(gè)當(dāng)前復(fù)用的cell隊(duì)列(或列表),并且每一個(gè)cell都擁有自己的標(biāo)識(shí)符,并這些cell能在代理對(duì)象的dequeueReusableCellWithIdentifier:的方法中獲取.
在tableview新建的時(shí)候,會(huì)新建一個(gè)復(fù)用池(reuse pool).這個(gè)復(fù)用池可能是一個(gè)隊(duì)列,或者是一個(gè)鏈表,保存著當(dāng)前的Cell.pool中的對(duì)象的復(fù)用標(biāo)識(shí)符就是reuseIdentifier,標(biāo)識(shí)著不同的種類的cell.所以調(diào)用dequeueReusableCellWithIdentifier:方法獲取cell.從pool中取出來(lái)的cell都是tableview展示的原型.無(wú)論之前有什么狀態(tài),全部都要設(shè)置一遍.
過(guò)程
在UITableView創(chuàng)建同時(shí),會(huì)創(chuàng)建一個(gè)空的復(fù)用池.之后UITableView在內(nèi)部維護(hù)這個(gè)復(fù)用池.一般情況下,有兩種用法,一種是在取出一個(gè)空的cell的時(shí)候再新建一個(gè).一種是預(yù)先注冊(cè)cell.之后再直接從復(fù)用池取出來(lái)用,不需要初始化.
-
UITableview第一次執(zhí)行tableView:cellForRowAtIndexPath:.當(dāng)前復(fù)用池為空.
dequeueReusableCellWithIdentifier調(diào)用中取出cell,并檢測(cè)cell是否存在.
目前并不存在任何cell,于是新建一個(gè)cell,執(zhí)行初始化, 并return cell.
代碼如下:#define kInputCellReuseIdentifierPWD @"password_cell" UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kInputCellReuseIdentifierPWD]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kInputCellReuseIdentifierPWD]; // other initialization i.e. add an UIButton to cell's contentView } // custom cell. i.e. change cell's title cell.textLabel.text = @"It is awesome"; return cell;
上面的代碼中,你返回的cell會(huì)被UITableView添加到復(fù)用池中.第二次調(diào)用tableView:cellForRowAtIndexPath:,當(dāng)前復(fù)用池中有一個(gè)cell.這時(shí)候因?yàn)?code>UITableView上面還未填滿,而且復(fù)用池中唯一的那一個(gè)已經(jīng)在使用了.
所以取出來(lái)的Cell仍然是nil.于是繼續(xù)新建一個(gè)cell并返回,復(fù)用池再添加一個(gè)cell,當(dāng)前復(fù)用池中cell的個(gè)數(shù)為2.
假如當(dāng)前tableview只能容納5個(gè)cell.那么在滾動(dòng)到第6個(gè)cell時(shí),從tableview的復(fù)用池取出來(lái)的cell將會(huì)是第0行的那個(gè)cell.以此類推,當(dāng)滾動(dòng)到第7行時(shí),會(huì)從復(fù)用池取出來(lái)第1行的那個(gè)cell. 另外,此時(shí)不再繼續(xù)往復(fù)用池添加新的cell.
-
另一個(gè)用法:在
UITableView初始化的時(shí)候,注冊(cè)一個(gè)UITableViewCell的類.[tableView registerClass:[UITableViewCell class] forCellWithReuseIdentifier:@"AssetCell"];
使用此方法之后,就不用再判斷取出來(lái)的cell是否為空,因?yàn)槿〕鰜?lái)的cell必定存在.調(diào)用dequeueReusableCellWithIdentifier:方法時(shí),會(huì)先判斷當(dāng)前復(fù)用池時(shí)候有可用復(fù)用cell.
如果沒(méi)有,tableview會(huì)在內(nèi)部幫我們新建一個(gè)cell,其他的跟方法一一樣.
這里有個(gè)動(dòng)畫可以很清晰地看到滑動(dòng)時(shí)的復(fù)用過(guò)程.圖片來(lái)自Scroll Views Inside Scroll Views

StoryBoard中的復(fù)用機(jī)制
與CSwater討論之后,覺得在StoryBoard中,復(fù)用機(jī)制如下:在對(duì)StoryBoard初始化UITableView時(shí),會(huì)再內(nèi)部調(diào)用registerClass:forCellWithReuseIdentifier:注冊(cè)我們?cè)趕toryboard上設(shè)置的cell.剩下的流程跟第二種方法一樣.
爬過(guò)的坑
從復(fù)用池中獲取cell的方法有兩個(gè):dequeueReusableCellWithIdentifier:forIndexPath:
和dequeueReusableCellWithIdentifier:.還記得我們?cè)诘诙€(gè)方法對(duì)使用使用復(fù)用池之前的情況嗎? 對(duì),就是注冊(cè)一個(gè)cell.注冊(cè)之后我們調(diào)用dequeueReusableCellWithIdentifier:獲取cell.
對(duì)于第二種情況,兩種方法都是沒(méi)問(wèn)題的.但是,在調(diào)用registerClass:forCellReuseIdentifier:之前,你必須注冊(cè)一個(gè)cell類.正如官方文檔所言:
IMPORTANT
You must register a class or nib file using theregisterNib:forCellReuseIdentifier:orregisterClass:forCellReuseIdentifier:method before calling this method.
總結(jié)
UITableViewCell的復(fù)用機(jī)制是,在tableview中存在一個(gè)復(fù)用池.這個(gè)復(fù)用池是一個(gè)隊(duì)列或一個(gè)鏈表.然后通過(guò)dequeueReusableCellWithIdentifier:獲取一個(gè)cell,如果當(dāng)前cell不存在,即新建一個(gè)cell,并將當(dāng)前cell添加進(jìn)復(fù)用池中.如果當(dāng)前的cell數(shù)量已經(jīng)到過(guò)tableview所能容納的個(gè)數(shù),則會(huì)在滾動(dòng)到下一個(gè)cell時(shí),自動(dòng)取出之前的cell并設(shè)置內(nèi)容.
擴(kuò)展知識(shí)
其實(shí),UITableViewCell的復(fù)用機(jī)制并非什么黑魔法,而是設(shè)計(jì)模式中的一種創(chuàng)造型設(shè)計(jì)模式--對(duì)象池設(shè)計(jì)模式
The object pool pattern is a software creational design pattern that uses a set of initialized objects kept ready to use – a "pool" – rather than allocating and destroying them on demand. A client of the pool will request an object from the pool and perform operations on the returned object. When the client has finished, it returns the object to the pool rather than destroying it; this can be done manually or automatically. ---- wikipedia
大意是,對(duì)象池模式是一種創(chuàng)造型設(shè)計(jì)模式,使用一個(gè)已初始化對(duì)象的集合,并能隨時(shí)從”池”中拿出來(lái)使用.以此避免對(duì)象的創(chuàng)建銷毀所帶來(lái)的開銷.一個(gè)客戶端的池會(huì)請(qǐng)求池中的對(duì)象,然后在返回的對(duì)象上執(zhí)行操作.當(dāng)這個(gè)客戶端結(jié)束時(shí),代替原本銷毀操作,取而代之的是將對(duì)象返回到池中.這個(gè)操作可以手動(dòng)執(zhí)行,也可以自動(dòng)執(zhí)行.有一個(gè)明顯的有點(diǎn)就是面對(duì)數(shù)據(jù)量很大時(shí),能很好的改善性能.更多的請(qǐng)看維基百科.
參考文章
Object pool pattern
Scroll Views Inside Scroll Views
UITableViewDataSource Protocol Reference
UITableView