WWDC 2019

SwiftUI

终于可以彻底离开ObjectiveC。看演示的例子应该简化的不少,尤其是不用考虑一些靠近OS底层的通用功能,例如dark mode、自适应字体大小等。一年多没有写iOS了,看来又得花几天看看SwiftUI的教程和练练手,要不再过一年,想写写一些hobby的app都捡不起来了。糊口技术更新快是码农的现实。

Single code base: iPhone/iPad/Mac,以及新的iPadOS

iPad作为大屏幕iPhone的日子到头了,开始尝试覆盖一些MacBook的使用场景,慢慢变成一台触屏MacBook。我预言,三五年之后,MacBook将淡出,基本日常生活工作都可以在iPad上顺利进行(当然,在工作上,外接物理键盘估计免不了)。Mac Pro还是会有市场的,需要强大算力的工作,还是离不开desktop。

强大的新Pro和Monitor

这也是上面想法的一个不错的印证。

AR和Minecraft

还是半吊子,这样玩Minecraft手不累吗?AR的话,还是继续耐心等待几个版本之后的Hololens和Oculus。

Sign In with Apple

姗姗来迟。对于普通用户,又多了一个button。普通用户才不知道你后面在隐私方面做了多少改进,就是yet another button。对于大部分码农,这只支持苹果系统,在部署决策上一定属于二等公民。

 

iTunes分成三个app之类的,我不太关心,反正我从来就不喜欢iTunes,也不用iTunes。

觉得这次苹果中规中矩。之前还在纳闷苹果的软件部门都在干嘛,因为不见大飞跃,反而bug不断。原来团队里牛人的精力都集中在iPadOS之类的大方向迁移,那还算靠谱。

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.

Moving a placeholder site from Squarespace to Github Page

I have been owning a domain and an LLC with the same name.

Squarespace was my choice since I was about put in more than just a placeholder. $144/year was not bad considering the time it saves me given the features, designs, and flexibility it provides.

However, it ends up a nice looking one-pager is all I need for now. The yearly renewal is around the corner. I decided to move it to Github Page, which is totally free.

Here are the steps:

  1. Create a Github Page.
    1. Login to Github with your username (e.g., examplegithublogin)
    2. Create a new empty repo, examplegithublogin.github.io
    3. Use SourceTree to check it out to a folder locally
    4. Find a free template (on https://www.free-css.com/ etc.), put in the folder, commit and push.
    5. Go to the site (examplegithublogin.github.io) directly. Double check everything works fine.
  2. Unlink from Squarespace
    1. log in to Squarespace -> settings -> domains, then Unlink
    2. If needed, stop auto-renew on Squarespace.
  3. Setup A record
    1. For example, if your domain is registered with Google domains, log in to Google Domains
    2. Remove the Squarespace “Synthetic records”
    3. Add a “@” record, points to the following 4 IP’s (185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153).
  4. Add the custom domain to Github Page settings
    1. Go back to the examplegithublogin.github.io repo -> settings -> GitHub Pages -> Custom domain -> put yourcustomdomain.com in -> save
  5. Check yourcustomdomain.com and http://www.yourcustomdomain.com (with and without https://)
    1. It works for me immediately, but it could take you more time depends on your DNS propagation.
    2. For me, SSL took about 3 mins to start working. (Github needs to issue a certificate behind the scene.)
  6. Done.