回頭看之UITableView-(基本代理方法及復(fù)用原理)

UITableVIew是iOS開發(fā)中最常見的視圖中最經(jīng)典的視圖了,沒有之一智亮,相信對(duì)這個(gè)視圖敢稱精通的人開發(fā)個(gè)好應(yīng)用應(yīng)該是問題不大的退疫。


閑話少敘,進(jìn)入正題鸽素。

怎么使用

掌握兩個(gè)代理

  1. UITableViewDelegate

    @optional  
    //下文再提到該方法用heightForRow代替
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
    
  2. UITableViewDataSource
    @required
    //下文再提到該方法用numberOfRowsInSection代替
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
    //下文再提到該方法用cellForRow代替
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

    @optional
    //下文再提到該方法用numberOfSection代替
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    

要想比較完整的展示你的數(shù)據(jù)褒繁,這四個(gè)方法是最經(jīng)常被實(shí)現(xiàn)的。
調(diào)用過程大體是這樣的:tableView會(huì)先詢問代理(在一般MVC里大部分是當(dāng)前視圖控制器ViewController)要展示多少個(gè)section,就是調(diào)用numberOfSections,如果代理沒有實(shí)現(xiàn)該方法默認(rèn)就是1個(gè)section馍忽。然后tableView調(diào)用numberOfRowsInSection先詢問第一個(gè)section有多少個(gè)cell,然后挨個(gè)執(zhí)行heightForRow獲取每個(gè)cell的高度棒坏。tableView對(duì)每個(gè)section都執(zhí)行一遍這樣的操作后,那么結(jié)果來了:tableView通過對(duì)這些cell高度的累加就知道了需要多大的空間才能安放得了所有的內(nèi)容遭笋,于是它調(diào)整好了contentSize的值坝冕。這樣走下來就為我們后續(xù)在滑動(dòng)時(shí)能通過scrollIndicator觀察到我們大體滑到了哪個(gè)位置做好了準(zhǔn)備。
準(zhǔn)備好空間之后接下來的任務(wù)就是準(zhǔn)備內(nèi)容了瓦呼。當(dāng)然大家都知道真正的內(nèi)容是依附在UITableViewCell上的喂窟,tableView先調(diào)用cellForRow去獲取代理返回給它的第一個(gè)cell,對(duì)于所有的cell來說width都是固定的央串,即tableView本身的寬度磨澡,對(duì)于第一個(gè)cell來說它的origin也是確定的,即(0,0)质和,也就是說要想確定這個(gè)cell的位置就只需要知道它的height了稳摄。于是tableView再去調(diào)用heightForRow去獲取它的高度,這樣一個(gè)視圖能確定顯示在屏幕什么位置的充要條件就具備了饲宿。剩下的cell同理厦酬,挨個(gè)放在上一個(gè)cell的下邊就行了。

總結(jié)一下:

  1. 調(diào)用numberOfSection獲得 A個(gè) section
  2. 先調(diào)用numberOfRowsInSection獲得B個(gè)cell,再調(diào)用heightForRowB次瘫想。如此循環(huán)A次
  3. 循環(huán)調(diào)用cellForRowheightForRow仗阅,直到cell的個(gè)數(shù)充滿當(dāng)前屏幕。

這就是一個(gè)普通的tableView一開始加載數(shù)據(jù)的過程国夜,有幾點(diǎn)需要說明:

  1. 如果你展示在每個(gè)cell上的內(nèi)容是相對(duì)固定的减噪,準(zhǔn)確點(diǎn)說是每個(gè)cell的高度是固定的,那么heightForRow是不建議讓代理去實(shí)現(xiàn)的,而是通過tableViewrowHeight屬性來代替旋廷,當(dāng)數(shù)據(jù)量比較大鸠按,比如說有10000個(gè)(其實(shí)只要 >= 2)cell時(shí)礼搁,tableView只需要10000*rowHeight就知道應(yīng)該準(zhǔn)備的空間大小了饶碘,而不是調(diào)用一個(gè)方法10000次通過累加獲知需要的大小。而且你懂的馒吴,要想獲取一個(gè)cell的高度并不是那么容易的事扎运,尤其是在自動(dòng)布局出現(xiàn)之前,你需要計(jì)算各種字符串的所占空間的大小饮戳,這對(duì)性能是相當(dāng)大的損耗豪治。
  2. 如果每個(gè)cell高度確實(shí)不一樣,數(shù)據(jù)量又很大時(shí)該怎么解決這個(gè)性能問題呢扯罐,iOS7之后系統(tǒng)提供了估算高度的辦法负拟,estimatedRowHeight- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath//下文再提到該方法用estimateHeightForRow代替,這樣每次在加載數(shù)據(jù)之前,tableView不再通過heightForRow消耗大量的性能獲取空間大小了歹河,而是通過在estimateRowHeight或者estimatedHeightForRow不需要費(fèi)勁計(jì)算就能獲取的一個(gè)估算值來獲取一個(gè)大體的空間大小掩浙,等到真正的加載數(shù)據(jù)時(shí)才根據(jù)獲取真實(shí)數(shù)據(jù),并做出相應(yīng)的調(diào)整秸歧,比如contentSize或者scrollIndicator的位置厨姚。關(guān)于動(dòng)態(tài)計(jì)算高度,推薦羊教授的一篇文章優(yōu)化UITableViewCell高度計(jì)算的那些事
  3. 這些方法的調(diào)用在保證大順序不變的情況下键菱,每個(gè)方法的調(diào)用次數(shù)是不一定的谬墙,每個(gè)iOS版本又不一樣,你如果想知道可以動(dòng)手去試驗(yàn)一下经备。尤其是在iOS8,它認(rèn)為cell會(huì)隨時(shí)變化拭抬,所以一滑動(dòng)就重新計(jì)算cell的高度。
  4. 這些方法的調(diào)用其實(shí)也是有插曲的侵蒙,比如調(diào)用了reloadData之后玖喘,tableView只會(huì)調(diào)用能讓它知道所需空間大小的代理方法,然后立馬執(zhí)行reloadData之后的語句蘑志,也就說cellForRow并不會(huì)在reloadData之后緊接著執(zhí)行累奈。所以reloadData之后盡量避免對(duì)數(shù)據(jù)源數(shù)組的操作。

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

了解UITableView的人肯定對(duì)這一著名特性多少有點(diǎn)了解急但。咱們先假設(shè)UITableView沒有復(fù)用機(jī)制澎媒,那么我們要展示10000條數(shù)據(jù)的話,那就得生成10000個(gè)UITableViewCell,占用了大量?jī)?nèi)存不說波桩,性能也可想而知了戒努,必然是一滑一卡頓,一頓一暴怒啊,控制力弱的估計(jì)要摔手機(jī)了储玫。

復(fù)用機(jī)制大體是這樣:UITableView首先加載一屏幕(假設(shè)UITableView的大小是整個(gè)屏幕的大小)所需要的UITableViewCell,具體個(gè)數(shù)要根據(jù)每個(gè)cell的高度而定侍筛,總之肯定要鋪滿整個(gè)屏幕,更準(zhǔn)確說當(dāng)前加載的cell的高度要大于屏幕高度。然后你往上滑動(dòng)撒穷,想要查看更多的內(nèi)容匣椰,那么肯定需要一個(gè)新的cell放在已經(jīng)存在內(nèi)容的下邊。這時(shí)候先不去生成端礼,而是先去UITableView自己的一個(gè)資源池里去獲取禽笑。這個(gè)資源池里放了已經(jīng)生成的而且能用的cell。如果資源池是空的話才會(huì)主動(dòng)生成一個(gè)新的cell蛤奥。那么這個(gè)資源池里的cell又來自哪里呢佳镜?當(dāng)你滑動(dòng)時(shí)視圖是,位于最頂部的cell會(huì)相應(yīng)的往上滑動(dòng)凡桥,直到它徹底消失在屏幕上蟀伸,消失的cell去了哪里呢?你肯定想到了缅刽,是的啊掏,它被UITableView放到資源池里了。其他cell也是這樣拷恨,只要一滑出屏幕就放入資源池脖律。這樣,有進(jìn)有出腕侄,總共需要大約一屏幕多一點(diǎn)的cell就夠了小泉。相對(duì)于1000來說節(jié)省的資源就是指數(shù)級(jí)啊,完美解決了性能問題冕杠。

iOS6之后我們一般在代碼里這樣處理cell

  1. 先注冊(cè)
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];

    [self.tableView registerNib:[UINib nibWithNibName:@"NibTableViewCell" bundle:nil] forCellReuseIdentifier:@"NibTableViewCell"];

  2. 在代理方法里獲取

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
       UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell" forIndexPath:indexPath];
       // do something
       return cell;
    }
    

那么具體在代碼里是怎么實(shí)現(xiàn)的呢微姊?我們可以大膽的猜測(cè)一下。

UITableView有幾個(gè)屬性(假想的):

NSMutableDictionary *registerCellInfo;
NSMutableDictionary *reusableCellsDictionary;
NSMutableArray *visibleCells;

我們推測(cè)兩個(gè)注冊(cè)方法的實(shí)現(xiàn)
- (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier{
[self.registerCellInfo setObject:nib forKey:identifier];
[self.registerCellsDictionary setObject:[NSMutableArray array] forKey:identifier];
}
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier{
[self.registerCellInfo setObject:cellClass forKey:identifier];
[self.registerCellsDictionary setObject:[NSMutableArray array] forKey:identifier];
}

然后推測(cè)最關(guān)鍵的獲取方法

- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath{
    //indexPath這個(gè)參數(shù)是為了重置`cell`的大小分预,相關(guān)的處理并不是本文的重點(diǎn)兢交,所以暫不實(shí)現(xiàn)
    NSMutableArray *array = self.reusableCellsDictionary[identifier];
    UITableViewCell *cell = nil;
    if(array.count){
        cell = array.lastObject;
        [self.visibleCells addObject:cell];
        [array removeLastObject];
    
    }else{
        id obj = self.registerCellInfo[identifier];
        if([obj isKindOfClass:[UINib class]]){
            cell = [[((UINib *)obj) instantiateWithOwner:nil options:nil] lastObject];
        }else{
            cell = [[(Class)obj alloc] init];
        }
        if(cell){
            [self.visibleCells addObject:cell];
         }
    }
    
    return cell;
}

??,請(qǐng)忽略以上所有推測(cè)方法的不嚴(yán)謹(jǐn)笼痹,許多該有的條件判斷并沒有去處理配喳。但是寫到這里相信親愛的讀者已經(jīng)了解了UITableView復(fù)用機(jī)制的原理了。現(xiàn)在凳干,你已經(jīng)具備了自己動(dòng)手寫一個(gè)UITableView的基礎(chǔ)了(當(dāng)然晴裹,假設(shè)你已經(jīng)對(duì)UIScrollView有了充足的了解)。如果我的文章對(duì)你有用救赐,煩請(qǐng)點(diǎn)個(gè)喜歡涧团,好激勵(lì)我繼續(xù)寫下去。。泌绣。

關(guān)于UITableView的更多知識(shí)我們后續(xù)再談

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钮追,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阿迈,更是在濱河造成了極大的恐慌元媚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仿滔,死亡現(xiàn)場(chǎng)離奇詭異惠毁,居然都是意外死亡犹芹,警方通過查閱死者的電腦和手機(jī)崎页,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腰埂,“玉大人飒焦,你說我怎么就攤上這事∮炝” “怎么了牺荠?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)驴一。 經(jīng)常有香客問我休雌,道長(zhǎng),這世上最難降的妖魔是什么肝断? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任杈曲,我火速辦了婚禮,結(jié)果婚禮上胸懈,老公的妹妹穿的比我還像新娘担扑。我一直安慰自己,他們只是感情好趣钱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布涌献。 她就那樣靜靜地躺著,像睡著了一般首有。 火紅的嫁衣襯著肌膚如雪燕垃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天井联,我揣著相機(jī)與錄音卜壕,去河邊找鬼。 笑死低矮,一個(gè)胖子當(dāng)著我的面吹牛印叁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼轮蜕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼昨悼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跃洛,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤率触,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后汇竭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體葱蝗,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年细燎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了两曼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡玻驻,死狀恐怖悼凑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情璧瞬,我是刑警寧澤户辫,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站嗤锉,受9級(jí)特大地震影響渔欢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瘟忱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一奥额、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酷誓,春花似錦披坏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玫氢,卻和暖如春帚屉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漾峡。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工攻旦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人生逸。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓牢屋,卻偏偏與公主長(zhǎng)得像且预,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烙无,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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