The other day I wrote a post here on Medium about how I made an artsy app based on science. As a result of that, I got a considerable increase in the number of downloads, and a few more crashes than I liked to see.
The crash reports were in fact quite mysterious until I actually fired up my app in my iPad. The app seemed fine, but when I dared touch the floating top bar, the app crashed. Badly. In fact, so badly that it took me a whole day to figure it out.
Lesson 1: when your girlfriend asks you if you checked that your app works alright before publicising it widely, you listen to her.
The app was crashing because of how the various gesture recognisers were set up. The gesture recognisers are those bits of software that detect how your fingers move on the screen and make your apps perform various things. It looks like, in my case, the GRs were set up in a cyclic graph. This is bad news. So bad that I need a sketch to explain it.
Graph 1 is called an acyclic graph. It has no cycles. There is only one way to get from any node to any other. When your finger touches the screen of your iPad, it generates an event that is propagated to all the little thingies in your app that may want to handle it. These thingies are typically arranged in an acyclic graph. When gesture recognisers are set up correctly, they more or less mimic the structure of those thingies. Overly simplifying, if your event starts from A, it goes down to B, then “simultaneously” to D and C, then from C to E, and finally to F.
However, if the gesture recognisers are set up badly, they can end up in a situation like that in graph 2. That is called a cyclic graph. There can be multiple paths — of infinite length, even! — to go from any node to any other node. Assuming that every node propagates the touch event to any of its connections that are not the one the event came to them from — and this is what makes acyclic graphs work — you get from node A to node B, then to nodes C and D. C and D then go to E, but E only gets one of those events at any single time, so if E gets the event from C, then it has to send it to F and to D. F is fine, but D has to propagate the event to all of its outlets, meaning back to B, which will send it to A and C. Rinse and repeat, you got yourself an infinite loop. Infinite loops are mostly bad news.
I included graph 3 — a directed cyclic graph — because it may mitigate some of the problems of a cyclic graph, but it may not work in all cases. The bottom line is: if you are routing stuff in a graph, either you make sure that after a while your stuff “dies”, and at a sensible rate, or you keep track of what you sent around already, wasting precious memory, or you just avoid cyclic graphs for the sake of us all.
Now, I don’t know anything about the internals of iOS, but given that the error message mentioned graphs and gesture recognisers, I guess something got somehow messed up in a similar fashion. That didn’t help me very much, neither did the fact that the bug only seemed to affect iOS 10. The least helpful bit of all was that not very many developers were encountering such crash. One guy managed to reproduce it with a curious edge case in which the user interface was initialised at a certain time in the app when it should sanely not have been.
As I mentioned, I use Cinder as my app’s backbone. Cinder is not explicitly designed to make apps, and most of the times you don’t use the native OS’s controls to make a user interface for Cinder apps, so the way one has to initialise the app to make it fit inside a regular UIKit frame is a bit counterintuitive. Anyway, going through my own code, and the code of the example that comes with Cinder, I noticed that I was initialising my surrounding interface using Storyboards — an otherwise very nice technology — instead of doing it with my bare hands. As it turns out, Storyboards mess with the order of initialising things around the UI, and so likely caused the gesture recognisers to end up in an inconsistent state.
Et voilà, one line of code.
What pissed me off the most is that, prior to iOS 10, Storyboards were not messing this up. However, the way Apple does things behind the scenes — in fact, the way any closed and opaque technology does things behind the scenes — is what prevented me from figuring this out more easily.
Lesson 2: when a beta of iOS — or any sufficiently closed and opaque technology upon which you depend — comes up you run for your life and check that all your apps still work while you have time.
The new, fixed version of Circular Bells is out now, and still free! Please excuse the hiccup — reads: major fuck-up — and enjoy! And if you feel generous, you can always throw some coins at me, I won’t complain!