Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the exact order of UITableViewDelegate Methods - Strange behavior

Im doing some research to get tableview delegate methods exact order of execution.

Case 1: Number of rows - For small values

So firstly I have created 100 rows and found the execution flow as below.

  1. numberOfSectionsInTableView
  2. numberOfRowsInSection
  3. cellForRowAtIndexPath

Case 2: Number of rows - Above Int limit

I tried returning value greater than int limit

Then found an exception thrown. Works great.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
 reason: 'Failed to allocate data stores for 402337200000000 rows in section 0.
 Consider using fewer rows'

Case 3: Number of rows - Int value but higher

After that I changed the number of rows to 4023372000000 an ideal value but confirms to the return type Int.

At that time it takes more than 5 minutes and any of the delegate method was getting called after it gotta crashed. stuck before cellForRowAt getting called. Also my system got stucked for that much time. No logs on crash.

memory usage during stuck

Memory usage goes high

free space= -50GB(using my system memory too) Percentage usage = 600%

If they are sure it is gonna crash then this range should be included in Exception case.

Did they doing something else during that time?

In case 3 the compiler is doing something else and the concept "Only cells that is to be displayed in screen will be loaded for tableview" is failing.

like image 362
Saranjith Avatar asked Nov 02 '17 06:11

Saranjith


1 Answers

The title of your question, "What is the exact order of UITableViewDelegate methods", presumes there is a defined order. The methods you identify:

  • numberOfSectionsInTableView:
  • numberOfRowsInSection:
  • cellForRowAt:

are in fact defined in the UITableViewDataSource protocol which makes no mention of the order in which the methods are called. There seems to be a "logical" order, which is broadly what you observe in your Case 1. However, there are exceptions to this: in my testing, numberOfSectionsInTableView: gets called twice in succession, without the other methods being called in between.

In your Case 2 you specify a huge number of rows, and the tableView therefore throws an exception. This seems understandable: the tableView is unable to "allocate data stores" for all those rows. If it is storing even 1 bit of information for each row, that's 46 Terabytes. Not really surprising it throws an exception.

Your Case 3 is obviously middle ground. Why no immediate exception? Who knows. The space requirement would be 468 Gigabytes, so far more than any current device, but within the bounds of credibility for a few years hence. Given that iOS runs on numerous different devices with different memory sizes, some 32bit, some 64bit, etc, the Apple Developers can't, for any given number of rows, be "sure it's gonna crash". So plainly they've decided/defaulted to trying.

So what is happening after calling numberOfRowsInSection and before calling cellForRowAt (or crashing)? The tableView is obviously doing something to try to fulfil the request for those rows.

The UITableViewDelegate protocol defines several methods under the heading "Configuring Rows for the Table View":

  • heightForRowAt:
  • estimatedHeightForRowAt:
  • indentationLevelForRowAt:
  • willDisplay:forRowAt:
  • shouldSpringLoadRowAt:with:

These are all optional methods, but implementing them might give some clue as to what's going on. From what I can see, heightForRowAt is called after cellForRowAt (so only when a cell has been allocated to a particular row). But it looks like estimatedHeightForRowAt is called separately for each and every row, after numberOfRowsInSection and before cellForRowAt.

Why should the tableView want to know the estimated height of all the rows, before loading a cell for even one of them? My guess is that the tableView is trying to determine its overall contentSize: adding up the heights of all the rows to get the total height. Implementing estimatedHeightForRowAt is obviously adding to the tableView's work and so slowing it down, but even if you have not implemented that method, the tableView must be doing something to calculate the height of each and every row: that takes time and memory.

Turning to your conclusion, "the concept 'Only cells that is to be displayed in screen will be loaded for tableview' is failing": note that the memory being used and the time taken is related to the number of rows, NOT the number of cells. The method names all refer to ...*row*At. Even the exception in Case 2 refers to the data stores for rows. If you log cellForRowAt you will see that it is called only for as many rows as are on screen, give or take a couple, until you scroll. Scrolling causes cells to go off screen, at which point the tableView puts them in its queue ready to be reused ("dequeued") for a row about to appear on screen. So your conclusion is wrong: cells are only loaded for rows appearing on screen.

like image 171
pbasdf Avatar answered Nov 08 '22 06:11

pbasdf