keymanapp / keyman

Keyman cross platform input methods system running on Android, iOS, Linux, macOS, Windows and mobile and desktop web
https://keyman.com/
Other
387 stars 107 forks source link

feat(ios): Improve system keyboard load performance #1492

Open jahorton opened 5 years ago

jahorton commented 5 years ago

Is your feature request related to a problem? Please describe. The app has received a number of crash reports (see #1169, #1170, #1172) that are caused by poor system-keyboard startup performance. If a keyboard app-extension takes too long to load, iOS explicitly kills it.

Describe the solution you'd like As documented on #1169, profiling SWKeyboard's startup time should yield some insight on what components could be sped up, delayed, or performed asynchronously in order to promote responsiveness, both to the user and to the system.

Describe alternatives you've considered Apple doesn't really give the app alternatives.

Product context iOS

Additional context

1475 was a good first step toward addressing these issues.

image

Based on the final profiling obtained during that work, finding a way to streamline Manager's load time, perhaps by reducing the amount of data necessary to retrieve from 'app group' space at startup, would probably be a decent place to start. Less important but also possible would be to delay creation of the WKWebView, though that may have UI/user impact.

jahorton commented 4 weeks ago

Just revisited this today. Took me a while to get profiling, and even longer to actually find SWKeyboard entries as described, but I finally found it... along with something quite interesting.

profile excerpt

The biggest thing that stands out to me: what on earth is causing FontManager.registerCustomFonts() to spend such a proportionately high amount of time, and during startup at that?

After a bit further of a drill-down on that angle... we get to [UIFont fontNamesForFamilyName:] from UIFoundation, which accounts for 156.84 Mc of its 196.18 Mc. That's triggered here:

https://github.com/keymanapp/keyman/blob/69a3fa1795a457e49608e9f80561318d6dc556b7/ios/engine/KMEI/KeymanEngine/Classes/Resource%20Data/FontManager.swift#L159-L163

There has to be a better way to do this. Even if not, surely we don't have to do it immediately during system keyboard startup?


There has to be a better way to do this.

Even if there's no clear OS-provided method we could use, FontManager itself is quite, quite unoptimized about this operation.

Note the structure of the code block from above: we do a two-layer loop to detect if one font exists.

There are two more loop layers on top of this:

https://github.com/keymanapp/keyman/blob/69a3fa1795a457e49608e9f80561318d6dc556b7/ios/engine/KMEI/KeymanEngine/Classes/Resource%20Data/FontManager.swift#L30-L39

https://github.com/keymanapp/keyman/blob/69a3fa1795a457e49608e9f80561318d6dc556b7/ios/engine/KMEI/KeymanEngine/Classes/Resource%20Data/FontManager.swift#L137-L146

registerFont directly calls fontExists each time it is called.

So... we'd probably be more efficient there if we just batched up all fonts to check first (two loops), and then check all of them in one pass through the fontExists double-loop.