Tangential Thinking
I don’t know if this term is one somebody else coined, or if it has other meanings elsewhere, but what it means to me is clear. Life is a series of points on a curve of no discernible shape. No orderly function produced it. There are as many bends and folds as there are numbingly straight passages. The only guarantee is that, as it has bent before, it will bend again. The chief mistake of the student of this line is to project its future course as a tangent from the present. Someday it will bend away from that projection, and the tangent that seems so sure now will vanish. This mistake is easy to make. There are many straight passages, some so long as to suggest a guiding hand. If there is a guiding hand, it seems bent on tempting the complacent into despair and the despairing into complacency. Guard your mind against both temptations.
How I Organize a Swift File
As a professional developer, it’s my job to work with the code that I’m given, even if it’s not ideal or aligned with my own coding style. That doesn’t mean I can’t have my preferences and peeves. Sometimes I inherit Swift code that looks like this:
The main thing this code has going for it is that it’s terse, which can be a good thing for some Swift code. But there are problems:
MARK:
comments aren’t uniformly applied, which makes it hard to tell at a glance when one section ends and the next begins.- Alternating use of single and double line breaks suggest incorrect impressions of logical groupings.
- There’s no overarching system to how the methods are grouped and ordered. Some are superclass overrides, others are custom methods, others are IBActions, etc. In order to know if a method is implemented, you have to read or search the entire file.
- Essential dependencies are exposed as read/write properties, even though they should only be set once. Most likely the only reason these properties are exposed as vars is because the view controller is initialized via a storyboard, which doesn’t permit custom init methods.
- Members aren’t given explicit access levels, so it isn’t clear to the reader which methods and properties are meant to be used by other members in the module, and which ones are just lazily defaulting to
internal
. - Documentation-level comments use a mix of two and three slash formatting, and are placed at inconsistent locations.
When I encounter code like that, I try to clean it up:
What’s different:
- The code is separated into sections by member type and access level. Properties are all above the
init
section, methods are below it. Both the property and method sections are further divided (roughly) by access level: Public/Internal, Overrides, Interface Builder, and Private. MARK:
headers are added at the top of every code section.- No more than one empty line is used between any two sections. No blank lines are placed between property declarations (except for those that have documentation).
- Everything that can be made private has been made private. This includes dependencies. Dependencies are passed in as arguments to a new static factory method which initializes and correctly configures the view controller from a storyboard. Interface Builder outlets and actions have also been marked private, since those should not be accessible outside of this class. Though this sacrifices the ability to use segues and storyboard references, the clarity and reliability gained via explicit “injected” dependencies far outweighs those losses.
- Documentation uses the style seen throughout the Swift Standard Library (three slashes, truncated to 80 character line lengths).
Here’s how it looks with some of the details above removed, in order to capture all code sections in one screenshot:
I don’t expect everyone to agree with my preferences. This is just what I like. But I think I can make pretty good objective arguments for the principles I’m trying to put into practice:
- Nothing is exposed to the module (or anything else for that matter) that isn’t expressly designed to be freely used at that access level.
- All external dependencies are explicitly required at (or near) init time, heavily discouraging (if not outright preventing) misuse.
- A consistent, logical organization is used when breaking up code sections, so it takes less effort to find a given method or property when you need to review it.
- Broader access levels are moved near the top so that the exposed API surface is easier to see without having to jump to a generated interface.
- Documentation uses platform-consistent formatting so it’s easier to distinguish from an implementation-detail comment.
Self-Driving Car Fleet Commercial: 2021
INT. BEDROOM - DAY
A teenage GIRL is sitting on her bed working on homework. Her head is bobbing to music bumping in her headphones.
A smartphone beeps from the bedside table. The girl picks it up. Chat bubbles appear in the air around her as she uses her phone.
MESSAGE FROM FRIEND
beach trip tonite?
GIRL (MESSAGING)
lets do it
CLOSEUP: PHONE SCREEN
The girl launches a brightly colored app on her phone. She presses a button that says “Day Trip”.
INT. BEDROOM - DAY
The girl swipes through her options on her phone. As she swipes, her dorm room dramatically swipes away, replaced by each destination, surrounding the girl like she’s being magically transported: downtown, nature hike, theme park. Each destination appears with a badge in the corner that reads “Travel time: N hrs”. She chooses a beach that’s two hours away. It looks like sunset at the beach.
CLOSEUP: PHONE SCREEN
A carousel appears with silhouettes of vehicles. The girl flicks through the available vehicle types. Each one has a badge in the corner that reads: “Up to N passengers” until she stops on an eight-passenger, fun-looking, rounded box on wheels. She selects it.
INT. BEDROOM – DAY
The bedroom has returned to normal. All these simulated smartphone actions are happening very quickly. You’re not meant to study them, only to perceive the gist of them as they whizz past.
A title hovers in the air, as if projected from her phone: “Invite Friends”. The girl taps her phone several times. Each time she taps, an avatar bubble appears in the air next to her. It’s all the friends she’s inviting on the beach trip.
The girl taps a big “Book It” button.
EXT. DORMITORY - AFTERNOON
A boxy-looking van pulls up outside the girl’s dorm. The cool-looking sidedoor juts open as the van rolls to a stop, electric motor idling. The girl’s FRIENDS are already inside, laughing, waving her in, bracelets jangling in the summer sun.
INT. VAN - AFTERNOON
Everyone inside the van is partying. A TV is playing a movie, or maybe a video game, or karaoke. All the seats face inward. There is no steering wheel, no bucket seats. The girl finds a spot on the wraparound bench seating and fastens her seatbelt like it’s a muscle memory.
EXT. BEACH - SUNSET
The van pulls up in the immediate foreground, perfectly-centered in our field of view. The sun is setting in the distance, so the van is heavily silhouetted. It’s shaped just like the silhouette we saw earlier in the smart phone app. The crazy door opens and silhouettes of the kids pour out of the van and onto the sand. As they run down to the water, the title appears in a bright, thin, white font:
U B E R
Future Imperfect
In an episode of the third season of Black Mirror, a woman pulls up to an electric gas station of the near future. It’s the wee hours of the night. Her electric car, a rental, has a low battery. It’s about to putter out. She goes to the — I’m not sure what to call it — pump and tries to plug in the charging cable. It won’t fit. She rummages through the trunk and can’t find a conversion cable. She begs the attendant, and then everyone else at the station, but no one has the cable. She’s stranded. She has no choice but to trek down the interstate on foot, hoping to hitch a hike.
That scene strikes me as precisely the bold future that we’re rocketing towards.
Examples abound.
Only one percent of Android users have access to the latest version of Android because carriers and manufacturers aren’t motivated to release them.
The next lightbulb you buy might be conscripted into an army of lightbulbs bent on bringing down the power grid on the eastern seaboard if its manufacturer isn’t obligated to use strong enough security measures. The few manufacturers that are using those standards might be too expensive for you or just won’t work with your other Things of the Internet.
Facebook for iOS still doesn’t use the native share sheet, which was released four years ago.
The new “TV” app for tvOS/iOS doesn’t include Netflix, arguably the most important streaming service, presumably because Netflix refused whatever terms Apple required.
There is no Amazon Prime Video app at all on tvOS, let alone in the new TV app. Comixology, another Amazon product, doesn’t sell comics in-app because the 30% markup on in-app purchases makes the idea a non-starter for Amazon. They can’t even link to the online store. You just have to know that there’s an online store where you can purchase the items, and that those items will magically appear in the app.
Google Maps, demonstrably the best mapping service in the world, can’t be configured as the default mapping app on your iPhone or iPad. Because of the ongoing competition between Apple and Google, it’s not even installed by default anymore1. Many iPhone owners will never discover just how much better Google Maps is.
Google’s speech-to-text recognition is fantastically good. Try it out in the Google iOS app sometime, and compare the same prompts with what you get from Siri. I’m talking about the difference between a barber jacket
and a Barbour jacket
. Google understands when you mean the latter. The default iOS keyboard has a voice recognition feature that isn’t nearly as good, but you can’t use Google’s speech recognition in Gboard, Google’s iOS keyboard, because Apple won’t allow them to access the microphone.
Five years and five operating systems later, Apple finally extended a public API for Siri, but it only works with six limited domains. You still can’t use Siri to put an item in the todo list app of your choice.
If you prefer Chrome over Safari on iOS, or the Gmail iOS app over Mail, you have to plod through the tedious procedure of launching those apps manually since system-wide features can only use the native apps.
Hilariously, you can spend $4299 dollars on a spanking new MacBook Pro and $969 dollars on an iPhone 7 Plus — both from the same manufacturer — but you cannot connect them together without a $25 conversion cable.
It is no wonder, then, that Google is staking their future on original hardware. There’s no way they can embed all of their fantastic services into a competitor’s device at a level that’s integrated deeply enough to be useful. The only way they can bring their AI assistant plans into concrete reality is to make their own phone. From a historical perspective, this is a pathetic waste of resources. Apple already makes the best hardware and software. The ideal smartphone would marry Apple’s hardware and software with Google’s services. But because of the intractable realities of competition and viable business models, Google has to reinvent Apple’s wheels in order to keep selling their own.
This is not a rant about the lack of open standards. Open standards don’t lead to a perfect user experience, either. The podcasting industry is, by tech standards, the wild west. But if you try sharing an episode with someone, they’ll be unable to hear it unless you share it using a podcast client that offers a proprietary browser player. New features in HTML and CSS are dependent upon mass market browser developers like Apple, Google, and Microsoft agreeing to implement spec changes. Their willingness to do so is a business decision, and could change in the future — <cough>
AMP</cough>
.
Please understand I am not suggesting that things could be any different. At least not practically. There are fixed points in business, law, and history that are determining our status quo. What I am saying is that our technological future is being shaped more by business constraints than engineering constraints. What is technically possible is outstripping what is feasible in the market.
The future is looking less and less like Star Trek and more like that woman in Black Mirror, thumb in the wind, begging for a ride.
-
By this I mean the original iOS Maps application used Google maps for it’s data, thus in a sense being installed by default. ↩
AsyncOperations
Today I’m open sourcing some utility classes I’ve been working on for the past several months. From the GitHub description: “A toolbox of NSOperation subclasses for a variety of asynchronous programming needs.”
Just Show Me The Source
AsyncOperation is the abstract base class used throughout.
AsyncBlockOperation offers simple asynchronous execution of a block.
AsyncTaskOperation manages multiple requests, passing a shared generic result back to all callers.
AsyncTaskQueue coalesces identical task operations so expensive work is only performed once.
Asynchronous NSOperations
Generally speaking, NSOperation makes it easy to chain together dependencies among multiple operations. Consider a sequence of NSBlockOperations:
let one = BlockOperation { print("One") } let two = BlockOperation { print("Two") } two.addDependency(one) // Prints: // One // Two
But what happens if you have a block that must be executed asynchronously?
let one = BlockOperation { doSomethingSlowly(completion:{ print("One") }) } let two = BlockOperation { print("Two") } two.addDependency(one) // Prints: // Two // One
There are at least two problems here. Of course our output is now printing in the wrong order, but notice also that there’s no way to cancel one
after it has called doSomethingSlowly()
. As far as NSOperationQueue is concerned, that operation has already finished, despite the fact that we haven’t yet received our result.
To solve both of these problems, we would need to change the behavior of NSBlockOperation so that it isn’t marked finished until we say so. Since we can’t change the behavior of that class, we’d have to write our own NSOperation subclass with that capability:
let one = MyAsynchrousOperation { finish in doSomethingSlowly(completion:{ print(“One”) finish() } } let two = BlockOperation { print("Two") } two.addDependency(one) // Prints: // One // Two
Writing NSOperation subclasses is something every Swift developer should know how to do, but it’s still a pain in the a**. It would be preferable to have an abstract base class that subclasses NSOperation, adding built-in support for asynchronous execution in a way that can be extended for any arbitrary purpose. That’s what AsyncOperations
aims to provide.
AsyncOperations
There are four classes in AsyncOperations:
AsyncOperation: An abstract base class that subclasses NSOperation. This class handles all the annoying boilerplate of an NSOperation subclass (including the KVO notifications around execution and cancellation). This class is not meant to be used directly, but via concrete subclasses. You can write your own subclasses, but there are two subclasses provided for you which cover common use cases.
AsyncBlockOperation: Similar to NSBLockOperation, except it only accepts a single execution block. The operation will not be marked finished until the execution block invokes its lone finish handler argument.
AsyncTaskOperation: This generic class provides support for associating multiple requests for a given result with a single operation. The shared result of the operation (of the generic
Result
type) will be distributed among all the operation’s active requests. You can use AsyncTaskOperation directly in your own NSOperationQueues, or you can use it implicitly viaAsyncTaskQueue
.AsyncTaskQueue: This generic class acts as a convenient wrapper around an NSOperationQueue of AsyncTaskOperations. It coalesces requested tasks with matching identifiers into a single task operation, so that expensive work is only performed once, even if it requested concurrently from isolated callers. A classic use case for this class would be in the implementation details of an offline image cache.
Examples
ImageCache.swift. A simplified version of an image cache that uses a private AsyncTaskQueue to coalesce concurrent requests for the same image into a single task operation, passing the resulting image back to all callers.
HeadRequestOperation.swift. A contrived example of a concrete AsyncOperation subclass, illustrating how a subclass must implement the required
execute(finish:)
method. This class makes a HEAD request for an arbitrary URL, returning the result via a completion block.Blocks.swift. Simple example of how you would chain together AsyncBlockOperations using the standard NSOperation dependency API.