[iOS7] Woes Unto Me, For I Am Undone

While I’m waiting for a full restore of my iPhone that will hopefully get me to a usable state again, there’s a couple of things you should know about iOS programming that has subtly changed.

First off: I like the new interface. I like the new OS. So this is not a mindless rant.

[UIKit]

UIKit has never been threadsafe. Ever. This is why we got used to performSelectorOnMainThread back in the pre-block days, then dispatch_async. The non-threadsafety of UIKit is a total mystery to me, seeing that I can change text in labels, images, and the like, mark the views as dirty, and then the main GUI should take care of the updates however long afterwards. That being said, Android does the same, so there must be a technical reason I’m not seeing. However, Android doesn’t even let me change anything. It’s an immediate exception at runtime. UIKit, on the other hand, lets me do pretty much anything I want, then crashes with a cryptic message, when there is one. After digging and step-by-step debugging, I usually find that what I assumed runs on the main thread, does not, in fact.

But that’s the kicker, right here. Up till iOS6, some conventions (or habits to be more accurate) led me to believe that this callback or notification would always be called on the main thread, thus not forcing me to use a very performance-expensive dispatch block. And then… it changes. The app starts behaving erratically, when it doesn’t outright crash. And debugging that erratic behavior is difficult, because it’s basically a race-condition. It could work just fine on an iPhone 4 but not on a 5, and vice-versa.

For a new codebase, I guess the problem doesn’t arise as much, but for us developers who work on older projects, sifting through thousands of lines of code to figure out which one causes that is time consuming, to say the least.

Case in point : navigation controllers. They used to be somewhat thread-safe, pushing and popping being stacked and executed one after the other. Of course, every now and again, you would have to tweak a little bit, make sure some essential data was loaded before pushing the next one, but nothing illogical.

In iOS 7, it’s totally asynchronous and unstacked. And unless you setup a delegate to make sure you detect the end of the animation, you can end up in very dark places. As soon as you have a more complex navigation than simple tree walking, something that worked reasonably well till now becomes very hard to maintain.

Let’s say I have a custom navigation bar that reflects some general data (ie not just a title with the name of the current screen you’re at). Now lets imagine some scenario where when you change something in one of the leaves of your navigation tree it requires a change in another leaf, where the user has to input something. You can say, for example, that you have a weather app, and in one leaf you have the general area of interesting data (wind, hygrometry, whatever), and in the other the units you want to use. Changing from one to another requires a change of units. So, naturally, you want to pop the view controller and push the units one. Except now, you can’t do it too fast. You have to wait for each animation to complete before taking the next action. And you really shouldn’t try to change the title while the animation is running either. (yes I know you could set the navigation hierarchy directly, but my rebuttal is the same as the one given afterwards)

And I hear someone say “well it’s easy, you have the two delegate methods for the navigation controller, so just use it”. Yeaaaaaaaaaah. Weeeeeeeell… The nav controller delegate has to be known at a higher level than the views you will push and pop, right? So basically, the app delegate would be the nav delegate. So the app delegate has to know about the underlying structure and the navigation paths of the whole application? Sorry, I do object oriented programming, I have no intention of having all my view controllers as instance variables of my delegate with a huge switch every time there is pop animation to determine whether I should wait for it to finish or not.

What would be a good alternative then?

  • If I had a notification I could subscribe to, that’d be swell. And that’s what I implemented at the higher level. But it’s a hack.
  • There could be a lock, too. [UINavigationController waitForAnimationToEnd] for instance.
  • Or a bool telling me whether the nav controller is in transition or not.
  • Hell, even an exception would be better than just putting the app in an uncertain state. At the very least I could break on it to find which one of my thousands of lines should be scrutinized.

Going the multi-app, multi-threaded, asynchronous, route, is fine by me. But we have to have the tools to do it properly. Even viewDidAppear is not a guarantee that the animation is done and changing the title or pushing a new view controller on the stack won’t give us a nice Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.. What did I do wrong that was perfectly fine up until 3 weeks ago? No idea. No exception. No information. Just “hey don’t do that”. Totally Kafkaian as far as I’m concerned, since I just hit on the back button. But now, after this point, any and all navigation related code will crash. And I’ll spend a few hours or even days to figure out why.

[Networking]

For the same reasons, networking (low level networking mostly, but with stuff creeping up sometimes higher) has changed. I can have network connectivity, tested, working fine in Safari, and a socket that connects to nothing while not timing out. Why? How?

Because that, too, has been moved to a new asynchronous mechanism that offers very little help. My NSURLConnection might appear to be doing nothing, but be active enough so that the system as a whole doesn’t deem it a timeout. And since I have no way of peering into its current state, apart from waiting patiently at the delegate points, the net result is that the app looks as if it’s stuck. Which it is, but not UI stuck. Can’t get out of that mode any other way than kill/restart for now.

I wish I had dug enough to give you more information about it, on par with the above UIKit problem, but the truth is it’s been 2 weeks and I still haven’t had the time to figure it out properly. And I have very little hair left. And it’s all grey.

[Conclusion]

I am for all the new changes. I like them, think they make sense, and overall improve and expand the possibilities for us developers. But without insider information as to how the thing works under the hood, we have very little ways of tackling these kinds of bugs. And all of them feel… hacky.

By all means, change the APIs, improve them, etc etc, but also give us the tools to do our jobs properly. Everybody wins if the users are happy.

And I wish I didn’t have to kill Mail so often, which seems to be stuck on the same problems I have. I guess that’s a sign that I’m not alone in my struggle.

  

Leave a Reply