UITableview是大家常用的UIKit組件之一,使用中我們最常遇到的就是對delegate和dataSource這兩個委托的使用。我們大多數人可能知道當reloadData這個方法被調用時,delegate和dataSource就會被回調,但是其中具體的細節,可能很多人不會去探究。我最近有興趣 ...
UITableview是大家常用的UIKit組件之一,使用中我們最常遇到的就是對delegate和dataSource這兩個委托的使用。我們大多數人可能知道當reloadData這個方法被調用時,delegate和dataSource就會被回調,但是其中具體的細節,可能很多人不會去探究。
我最近有興趣來探討這個問題是因為我最近遇到過dataSource中有的方法被調用,但是有的方法沒有被調用的情況,同時你會發現當tableview被add到一個superView的時候,也會觸發了reloadData一樣的回調。那麼這兩個委托究竟是怎麼執行的呢?
-
我們首先來看看蘋果文檔對reloadData的描述
Call this method to reload all the data that is used to construct the table, including cells, section headers and footers, index arrays, and so on. For efficiency, the table view redisplays only those rows that are visible. It adjusts offsets if the table shrinks as a result of the reload. The table view’s delegate or data source calls this method when it wants the table view to completely reload its data. It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates.
大致的意思就是說reload這個方法是用來構建table的,包括cell、section,而且只會對可見的行進行重新的繪製,當tableview想要完整的載入數據時,delegate和data source會調用此方法。增加刪除行,尤其是需要block動畫的時候不用用它。
從這裡只能看出個大概,並沒有解釋調用的原理。 -
那麼讓我們先寫一個最基本的tableview實現,然後對delegate和data source的回調設置一下斷點看看。
- (void)viewDidLoad { [super viewDidLoad]; UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)]; tableView.delegate = self; tableView.dataSource = self; [self.view addSubview:tableView]; // [tableView reloadData]; // [tableView layoutSubviews]; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 60; } -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 1; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return 20; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *cellID = @"cellID"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if(!cell){ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID]; } cell.textLabel.text = @"哈哈"; return cell; }
我們先對下麵這四個方法設置一下斷點,然後觀察左邊的棧信息。
首先被調用的是numberOfSectionsInTableView:
numberOfSectionsInTableView:調用棧信息.png
我們可以看到從addSubview是如何一步步調用到numberOfSectionsInTableView:的。
好的,我們下一步看斷點斷到了tableView:numberOfRowsInSection:
上。
tableView:numberOfRowsInSection:調用棧信息.png
_rebuildGeometry這個私有方法之前都是一樣的,所以我這裡並沒有全截,可以看到_rebuildGeometry中不僅調用了_updateRowData,還調用了一個_updateContentSize,從這裡來獲得每個section的行數。
我們接著往下看,到了tableView:heightForRowAtIndexPath:
tableView:heightForRowAtIndexPath:調用棧信息.png
這裡通過了一個block回調的方式獲取了各個row的高度,並決定了整個section的高度。
然後我們會發現,以上的幾個方法還會再被調用一遍:
numberOfSectionsInTableView:調用棧信息2.png
但是棧信息已經不一樣了,這次調用時由於tableview調用了layoutSubviews,而reloadData是layoutSubviews里調用的一個方法,因為layoutSubviews也是個公有的方法,所以我們可以用它來觸發reloadData。
斷點繼續執行,就執行到了tableView: cellForRowAtIndexPath:
,我們用它來獲取tableview每個row的cell。
tableView: cellForRowAtIndexPath:調用棧信息.png
我們會發現tableView: cellForRowAtIndexPath:並不是靠_rebuildGeometry下麵的方法來觸發,而只是靠layoutSubviews來觸發,如果layoutSubviews沒有執行成功,那麼就可能會遇到我之前遇到過的前幾個方法執行而tableView: cellForRowAtIndexPath:不執行的問題。
- 多瞭解UIKit的棧信息能夠幫我們瞭解蘋果運行的機制和原理,從而幫我們解決一些看起來非常詭異的bug,多看看蘋果的私有方法也有助於我們養成良好的編程習慣,我們儘量模仿蘋果的代碼規範無論是對自己寫代碼看著舒服,還是對他人來讀我們寫的代碼都一件好事。