UITableView concurrent issue with UIRefreshControl

The Error 

The exception is thrown in

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

It complains about out of index. For example, indexPath.row is 8, but the data set is empty.

Troubleshooting

The function is called because somehow UI is being updated. But the data set is empty means the data is in the middle of being refreshed. UI shouldn’t be asked to update if data is being refreshed. So need to find out the code causing the UI update.

Root Cause

self.refreshControl.endRefreshing() is called too early in the wrong place of the sequence. Moved it to queryCompletionBlock solve the problem (while refreshing data from iCloud).

keypath

What? allow referring to properties without actually invoking them.
Why do you need it? If you want to create an adapter for a property.

If keypath is used together with generics and associated type, the adapter can fit various types as well. Making it even more powerful and cleaner code.

See detail in this nice article.

Steps to performance tuning

Notes from Practical Approaches to Great App Performance

  • measure existing performance, set up baseline.
  • re-measure performance, compare, document, share.
  • focus on total impact (vs. just an area that are not used by many users, not used frequently)
  • reproduce -> profile -> modify -> repeat
  • good to have automated performance tests to avoid regressions over time (the thousand performance paper cuts)
  • important to understand user usage pattern, so that target is clear and optimizing on the most valuable areas.
  • break large task into smaller steps and write unit tests for each step. That helps pinpoint issue.
  • integration tests are measured from UX perspective, covering not just the task, but all other areas that work together.
  • always start with integration tests, so that you know what area to start the tuning.
  • use Time Profiler to get performance profile.
  • if not debugging threads related, better not to group the trace by thread. To disable it: Call Tree -> uncheck Separate by Thread.
  • how to remove noise, or focus on the signal
    • focus on one thread at a time. To do that: select only the thread that cost the most in the Track Filter panel
    • focus on one message at a time. To do that: select the suspecious in the Heaviest back trace panel
    • remove recursions. To do that: Call Tree -> check Flatten recursion.
    • drill down (bring up to the top level). To do that: right click on the trace -> Focus on subtree
    • hide all objc related messages in call tree (bubble those “cost of doing business” up to the parent callers). To do that: right click -> Charge 'libobjc.A.dylib' to callers
    • hide all small “contributors”. Say for a 2-sec sample range, filter out only trace that > 20ms, that means only focusing on contributors that cost >1%. To do that: Call trace constraints -> 20 for min.
  • even the slowness happens in system lib, there is possibilities to optimize
    • the data you passed into the system lib to operate
    • how many times you are calling this system method
    • system method is calling back into your code via delegates
  • key-value observer (KVO) (i.e., “update UI whenever model changes”) in a loop could impact performance
  • instant app launch means? 500ms to 600ms (that’s how long it takes the zoom animation from the home screen)
  • do initialization, such as DB init, in the background thread, to improve launch time.
  • as little work as possible in the initializers
  • load data on screen synchronously, off-screen data async.
  • strive for constant time.