It seems that Apple has been working very hard on “hiding latency” by showing a picture of an app’s last state while the app reloads. Apps may have to opt-in to this behavior, but it seems fairly prevalent on iOS and now OS X. Specifically, I’ve noticed it on iOS in some password-locked apps, in Music Studio, and on OS X, in the App Store.
After a firmware update, logging in again “re-opens” the App Store in precisely the state it was left after reboot. Even the update being installed is still shown as available, with its “RESTART” button grayed out. That perfectly represents how the app looked… before rebooting. But it rather quickly reverts to “Cannot contact the App Store; an Internet connection is required” since the wireless isn’t up yet.
Why it needs to talk to the App Store when it could have cached the data less than five minutes ago is beyond me, but never mind that.
The point is, while OS X is busy displaying a stale screenshot, any UI interaction will be lost. Because it’s not a UI at all, it’s a highly accurate view of what it could look like. OS X remains awfully confident about the number of updates it has, even though it can’t connect to the Internet and isn’t even loaded.
This is especially noticeable on iOS where the screen doesn’t visibly change state between the picture being shown and the real UI replacing it. I have a password-protected app that always looks like an “Enter your password” screen, complete with buttons in pixel-perfect position, but if I interact with it immediately after switching to it, my touches are dropped. Instead of 1234, it only registers 234 (or even 34). Then I wait for it to check the PIN and do the unlock animation, then realize that it won’t, and finally delete each digit by mashing Delete before entering the PIN for a second time.
1234 is not my real PIN, of course; that’s the PIN an idiot would have on his luggage. Or, you know, every Bluetooth device ever with a baked-in PIN code. (sigh)
It’s also really annoying when an app takes a while to (re)load because it’s legitimately large, like Music Studio and its collection of instruments for the active tracks. The UI is completely unresponsive for several seconds, until suddenly everything happens at once, badly. The Keyboard screen often gets key stuck down, making me tap it again to get it to release. Or knowing that, I avoid touching it for a while and have to guess at when it will be responsive, not knowing how much time I’ve wasted in over-shooting that point.
In the end, using static pictures of an app for latency hiding seems like a poor user interface—because the end of the latency period is also hidden, it encourages users to try interacting early, when the result is guaranteed not to work. But instead of showing the user that, it silently fails. I’d much prefer the older “rough outlines” splash screens than the literal screenshots of late; the “ready” transition is obvious with those, when the real UI shows up.
It actually surprises me that Apple would even release UI like this, because it’s kind of frustrating to clearly have an app on-screen that’s not reacting to me. Then again, with the train wreck that was QuickTime 4.0 Player, perhaps I shouldn’t be too surprised. (Yes, that was 15 years ago or something. No, the internet will never forget.)
Sunday, June 29, 2014
Saturday, June 7, 2014
Musing on Iterator Design
PHP’s standard library (not the standard set of extensions, but the thing known as the “SPL”) includes a set of iterators with a kind of curious structure.
At the root is Traversable, which is technically an empty interface, except that there’s some C-level magic to enable C-level things to declare themselves Traversable. This enables them to be used in
Iterator exists as the basic interface for regular PHP code to implement iteration, internally. Notably, while Traversable is a once-only iterator, Iterator is not.
For classes that wrap a collection, there’s an IteratorAggregate interface, which lets them return any Traversable to be used in their place. And who could forget ArrayObject, since a plain array (in spite of being iterable in the engine) is not actually a valid return from
There are some interesting compositions, like AppendIterator, which can take multiple Iterators and return each of their results in sequence. AppendIterator is a concrete class that implements OuterIterator, meaning that it not only provides regular iteration but the world can ask it for its inner iterator.
There’s the curiously named IteratorIterator, which I never appreciated until today, when I tried to pass a Traversable to AppendIterator. For no apparent reason, AppendIterator can only append Iterators, not Traversables. But IteratorIterator implements Iterator and can be constructed on Traversable, thus “upgrading” them.
Long ago discovered and just now remembered, PHP provides DirectoryIterator and RecursiveDirectoryIterator—but to make the latter actually recurse if used in a
The designer in me thinks this is all a bit weird. Because PHP defined the basic Iterator as rewind-able, a bunch of stuff that isn’t rewind-able (including IteratorAggregate) can’t be used in places that expect a base Iterator. Like AppendIterator. That in turn exposes a curious lack of lazy iterators. When I wrap an IteratorAggregate in an IteratorIterator,
So if I want to fake a SQL union operation by executing multiple database queries in sequence, and I don’t want to call the database before the AppendIterator reaches that point, there’s no simple way to do it with SPL’s built-in tools.
I can build a full Iterator that doesn’t query until
Or, I can build a full LazyAppendIterator that accepts Traversable and doesn’t try to resolve anything until the outer iteration actually reaches them. Then I can add IteratorAggregates that perform the query in
Those two choices amount to the exact same amount of work. In each case, I need to build a lazy iterator that tracks the “current iteration state” and launches the expensive call only when starting a new iterable/iteration sequence.
In other languages I’ve used, the basic one-pass/forward-only iterator is the type accepted by all the compositing types, and everyone must live with the idea that the iterators in use may not be seekable/replayable. In PHP, I have to live with the idea that the not all iterable things may work with all Iterators, which seems supremely odd.
P.S. They didn't pack any RegexFilterIterator in the box, either?
At the root is Traversable, which is technically an empty interface, except that there’s some C-level magic to enable C-level things to declare themselves Traversable. This enables them to be used in
foreach
loops even though they don’t support any Iterator method.Iterator exists as the basic interface for regular PHP code to implement iteration, internally. Notably, while Traversable is a once-only iterator, Iterator is not.
rewind()
is part of its interface.For classes that wrap a collection, there’s an IteratorAggregate interface, which lets them return any Traversable to be used in their place. And who could forget ArrayObject, since a plain array (in spite of being iterable in the engine) is not actually a valid return from
getIterator()
?There are some interesting compositions, like AppendIterator, which can take multiple Iterators and return each of their results in sequence. AppendIterator is a concrete class that implements OuterIterator, meaning that it not only provides regular iteration but the world can ask it for its inner iterator.
There’s the curiously named IteratorIterator, which I never appreciated until today, when I tried to pass a Traversable to AppendIterator. For no apparent reason, AppendIterator can only append Iterators, not Traversables. But IteratorIterator implements Iterator and can be constructed on Traversable, thus “upgrading” them.
Long ago discovered and just now remembered, PHP provides DirectoryIterator and RecursiveDirectoryIterator—but to make the latter actually recurse if used in a
foreach
loop, it needs to be wrapped in RecursiveIteratorIterator, because the caller of a plain RecursiveIterator must decide whether to recurse, or not, at an element with children.The designer in me thinks this is all a bit weird. Because PHP defined the basic Iterator as rewind-able, a bunch of stuff that isn’t rewind-able (including IteratorAggregate) can’t be used in places that expect a base Iterator. Like AppendIterator. That in turn exposes a curious lack of lazy iterators. When I wrap an IteratorAggregate in an IteratorIterator,
getIterator()
is called right then, before the result can even be added to an AppendIterator.So if I want to fake a SQL union operation by executing multiple database queries in sequence, and I don’t want to call the database before the AppendIterator reaches that point, there’s no simple way to do it with SPL’s built-in tools.
I can build a full Iterator that doesn’t query until
current()
is actually called, by implementing a handful of functions, each of which must carefully check the current iteration state before returning a value. These would satisfy the native AppendIterator, but be lazy themselves.Or, I can build a full LazyAppendIterator that accepts Traversable and doesn’t try to resolve anything until the outer iteration actually reaches them. Then I can add IteratorAggregates that perform the query in
getIterator()
, since I won’t be calling that right away.Those two choices amount to the exact same amount of work. In each case, I need to build a lazy iterator that tracks the “current iteration state” and launches the expensive call only when starting a new iterable/iteration sequence.
In other languages I’ve used, the basic one-pass/forward-only iterator is the type accepted by all the compositing types, and everyone must live with the idea that the iterators in use may not be seekable/replayable. In PHP, I have to live with the idea that the not all iterable things may work with all Iterators, which seems supremely odd.
P.S. They didn't pack any RegexFilterIterator in the box, either?
Subscribe to:
Posts (Atom)