Nov 10, 2018

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).

Nov 1, 2018

Requesting location "Always" authorization in iOS11+


Best practice: 
  • Ask for when-in-use authorization first
  • Ask to escalate authorization level to always at the moment when it is needed. 
    • use requestAlwaysAuthorization()
  • The user will be prompted only once. 

Oct 29, 2018

Method swizzling

What? changing the functionality of a method at runtime.
When to use? for the codes that you don't own but you want to modify its functionality, such as adding logging. (for the codes you have control over, just use extension.)
How? see Method swizzling in iOS swift

Oct 6, 2018

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.

Sep 16, 2018

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.

Jul 21, 2018

Load image: imageWithContentsOfFile vs. imageNamed

Use imageWithContentsOfFile only when you are sure it’s used once since iOS won’t cache it; otherwise, use imageNamed.

Jul 20, 2018

Access levels

  • open/public: cross-module
  • internal: the default, within same module
  • fileprivate: within same file
  • private: within same enclosing declaration and extensions

Jun 17, 2018

Live Rendering

Notes from IBInspectable / IBDesignable

user-defined runtime attributes: allows updating attribute value in Xcode

IBInspectable: easlier editable in Xcode

Can add inspectable property to existing classes via extension.

IBDesignable: WYSIWYG.

Use prepareForInterfaceBuilder() to provide dummy data at design time. It’s not run in shipping code.

Jun 9, 2018

Escaping closure

A clusure that is

  • passed in to a function as parameter
  • called after the function has returned

Closures are non-escaping by default in Swift 3+.

Use @escaping to mark it as escaping.

Jun 5, 2018

What slows down app launch

  • api calls
  • disk I/O
  • data preparation

May 25, 2018

let vs. read-only (computed)

  • let means constant, and cannot be set after initialized.
  • read-only also means cannot be set but since it is computed so it could return different value at different times.

May 9, 2018

Apr 19, 2018

QoS

Note: iOS 8+ should use QoS instead of system default queues (times showed are just examples)
  • User-interactive: UI feedback. Such as animation. (<1s)
  • User-initiated: UI works that required in order to continue the interaction. Such as opening a file. (1~3s)
  • Utility: UI works that have no need for an immediate result. Such as downloading a file. (3s~3m)
  • BackgroundNon-UI works. Such as indexing, backup. (3m~3h)

Feb 16, 2018

Location tracking and battery usage


  • Visits location service (CLVisit): most power-efficient way. Not for navigation or real-time. Good for doing async background processing related to location. Or to identify location pattern. Requires Always authorization. 
  • Region monitoring (Geofencing): Define a circular region and your app gets a notification when crossing the boundary. Apple's built-in Reminder app uses that too. Very little battery consumption. It is built upon Core Location's significant-change location service. Requires Always authorization. 
  • Significant-change location service: power-friendly way. Requires Always authorization. 
  • Standard location service: Real-time. Most power consumption.