UITableviewCell復(fù)用機(jī)制

前言

UITableview在iOS中的使用頻率是非常高的.通常,我們只需要通過設(shè)置代理,并且在代理方法tableView:cellForRowAtIndexPath: 調(diào)用dequeueReusableCellWithIdentifier:獲取cell并直接使用.但是從沒有細(xì)致得想過其中的過程與機(jī)制,并且知道最近面試(此次面試的問題)的時(shí)候,才發(fā)現(xiàn)自己表述得不太好.這種感覺就好像你每天都準(zhǔn)時(shí)吃飯(滑稽),突然有一天有人問你人為什么吃飯的時(shí)候自己卻沒能及時(shí)答上來的那種感覺.說明自己學(xué)得不夠扎實(shí).as we know,

如果你不能將知識(shí)通過簡潔的語言表達(dá)出來,那說明你還沒掌握這個(gè)知識(shí).

借此機(jī)會(huì),將這個(gè)知識(shí)點(diǎn)記錄下來

reuseIdentifier

對于reuseIdentifier,官方文檔是這樣解釋的:

The reuse identifier is associated with a UITableViewCell object 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 in initWithFrame:reuseIdentifier: and cannot be changed thereafter.
A UITableView object maintains a queue (or list) of the currently reusable cells, each with its own reuse identifier, and makes them available to the delegate in the dequeueReusableCellWithIdentifier: method.

冒死翻譯一下:
這個(gè)復(fù)用標(biāo)識(shí)關(guān)聯(lián)UITableviewCell對象,在tableview代理中創(chuàng)建帶有”標(biāo)識(shí)符”來復(fù)用cell對象,而且作為tableview多行顯示的原型(性能的原因).通過initWithFrame:reuseIdentifier:來指定一個(gè)cell對象而且在調(diào)用這個(gè)方法之后就不能修改了.在UITableView對象維護(hù)一個(gè)當(dāng)前復(fù)用的cell隊(duì)列(或列表),并且每一個(gè)cell都擁有自己的標(biāo)識(shí)符,并這些cell能在代理對象的dequeueReusableCellWithIdentifier:的方法中獲取.

tableview新建的時(shí)候,會(huì)新建一個(gè)復(fù)用池(reuse pool).這個(gè)復(fù)用池可能是一個(gè)隊(duì)列,或者是一個(gè)鏈表,保存著當(dāng)前的Cell.pool中的對象的復(fù)用標(biāo)識(shí)符就是reuseIdentifier,標(biāo)識(shí)著不同的種類的cell.所以調(diào)用dequeueReusableCellWithIdentifier:方法獲取cell.從pool中取出來的cell都是tableview展示的原型.無論之前有什么狀態(tài),全部都要設(shè)置一遍.

過程

UITableView創(chuàng)建同時(shí),會(huì)創(chuàng)建一個(gè)空的復(fù)用池.之后UITableView在內(nèi)部維護(hù)這個(gè)復(fù)用池.一般情況下,有兩種用法,一種是在取出一個(gè)空的cell的時(shí)候再新建一個(gè).一種是預(yù)先注冊cell.之后再直接從復(fù)用池取出來用,不需要初始化.

  • UITableview第一次執(zhí)行tableView:cellForRowAtIndexPath:.當(dāng)前復(fù)用池為空.
    dequeueReusableCellWithIdentifier調(diào)用中取出cell,并檢測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)在使用了.
所以取出來的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ù)用池取出來的cell將會(huì)是第0行的那個(gè)cell.以此類推,當(dāng)滾動(dòng)到第7行時(shí),會(huì)從復(fù)用池取出來第1行的那個(gè)cell. 另外,此時(shí)不再繼續(xù)往復(fù)用池添加新的cell.

  • 另一個(gè)用法:在UITableView初始化的時(shí)候,注冊一個(gè)UITableViewCell的類.

    [tableView registerClass:[UITableViewCell class]
             forCellWithReuseIdentifier:@"AssetCell"];
    

使用此方法之后,就不用再判斷取出來的cell是否為空,因?yàn)槿〕鰜淼腸ell必定存在.調(diào)用dequeueReusableCellWithIdentifier:方法時(shí),會(huì)先判斷當(dāng)前復(fù)用池時(shí)候有可用復(fù)用cell.
如果沒有,tableview會(huì)在內(nèi)部幫我們新建一個(gè)cell,其他的跟方法一一樣.
這里有個(gè)動(dòng)畫可以很清晰地看到滑動(dòng)時(shí)的復(fù)用過程.圖片來自Scroll Views Inside Scroll Views

復(fù)用cell的過程
復(fù)用cell的過程

StoryBoard中的復(fù)用機(jī)制

CSwater討論之后,覺得在StoryBoard中,復(fù)用機(jī)制如下:在對StoryBoard初始化UITableView時(shí),會(huì)再內(nèi)部調(diào)用registerClass:forCellWithReuseIdentifier:注冊我們在storyboard上設(shè)置的cell.剩下的流程跟第二種方法一樣.

爬過的坑

從復(fù)用池中獲取cell的方法有兩個(gè):dequeueReusableCellWithIdentifier:forIndexPath:
dequeueReusableCellWithIdentifier:.還記得我們在第二個(gè)方法對使用使用復(fù)用池之前的情況嗎? 對,就是注冊一個(gè)cell.注冊之后我們調(diào)用dequeueReusableCellWithIdentifier:獲取cell.
對于第二種情況,兩種方法都是沒問題的.但是,在調(diào)用registerClass:forCellReuseIdentifier:之前,你必須注冊一個(gè)cell類.正如官方文檔所言:

IMPORTANT
You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method.

總結(jié)

UITableViewCell的復(fù)用機(jī)制是,在tableview中存在一個(gè)復(fù)用池.這個(gè)復(fù)用池是一個(gè)隊(duì)列或一個(gè)鏈表.然后通過dequeueReusableCellWithIdentifier:獲取一個(gè)cell,如果當(dāng)前cell不存在,即新建一個(gè)cell,并將當(dāng)前cell添加進(jìn)復(fù)用池中.如果當(dāng)前的cell數(shù)量已經(jīng)到過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ì)模式--對象池設(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

大意是,對象池模式是一種創(chuàng)造型設(shè)計(jì)模式,使用一個(gè)已初始化對象的集合,并能隨時(shí)從”池”中拿出來使用.以此避免對象的創(chuàng)建銷毀所帶來的開銷.一個(gè)客戶端的池會(huì)請求池中的對象,然后在返回的對象上執(zhí)行操作.當(dāng)這個(gè)客戶端結(jié)束時(shí),代替原本銷毀操作,取而代之的是將對象返回到池中.這個(gè)操作可以手動(dòng)執(zhí)行,也可以自動(dòng)執(zhí)行.有一個(gè)明顯的有點(diǎn)就是面對數(shù)據(jù)量很大時(shí),能很好的改善性能.更多的請看維基百科.

參考文章

Object pool pattern
Scroll Views Inside Scroll Views
UITableViewDataSource Protocol Reference
UITableView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锌妻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奏赘,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)恼五,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哭懈,“玉大人灾馒,你說我怎么就攤上這事∏沧埽” “怎么了睬罗?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旭斥。 經(jīng)常有香客問我容达,道長,這世上最難降的妖魔是什么垂券? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任花盐,我火速辦了婚禮,結(jié)果婚禮上菇爪,老公的妹妹穿的比我還像新娘算芯。我一直安慰自己,他們只是感情好凳宙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布熙揍。 她就那樣靜靜地躺著,像睡著了一般氏涩。 火紅的嫁衣襯著肌膚如雪诈嘿。 梳的紋絲不亂的頭發(fā)上堪旧,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天,我揣著相機(jī)與錄音奖亚,去河邊找鬼淳梦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛昔字,可吹牛的內(nèi)容都是我干的爆袍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼作郭,長吁一口氣:“原來是場噩夢啊……” “哼陨囊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起夹攒,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蜘醋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咏尝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體压语,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年编检,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胎食。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡允懂,死狀恐怖厕怜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蕾总,我是刑警寧澤粥航,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站生百,受9級特大地震影響躁锡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜置侍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一映之、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜡坊,春花似錦杠输、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至据忘,卻和暖如春鹦牛,著一層夾襖步出監(jiān)牢的瞬間搞糕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工曼追, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窍仰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓礼殊,卻偏偏與公主長得像驹吮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子晶伦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容