Technical debt that lasts forever

I noticed that ls output is sorted case-sensitively on macOS; that is, “abc” is sorted after “Xyz.” It doesn’t appear there are any mechanisms to get ls to do a case-insensitive sort, either. To work around this in a script I was writing, I looked to sort to do this for me, and stumbled upon the always-confusing flag:

-f, --ignore-case: Convert all lowercase characters to their uppercase equivalent before comparison, that is, perform case-independent sorting.

Which brings about the question: what does -f have to do with case-insensitive sorting? The answer to this part of the mystery is more apparent in the coreutils version, which describes it as:

-f, --ignore-case: fold lower case to upper case characters

So the -f short-form flag is for “fold.” Case folding is a mechanism for comparing strings while mapping some characters to others, or in this case mapping lowercase to uppercase using Unicode’s Case Folding table.

The long-form version of this flag was added in 2001, citing as “add support for long options.” The short-form version was added in 1993, likely for compatibility with some pre-existing Unix version. The first version of the POSIX standard in “Commands and Utilities, Issue 4, Version 2” (1994, pg. 647) doesn’t even use the word “fold,” defining it as:

-f: Consider all lower-case characters that have upper-case equivalents, according to the current setting of LC_CTYPE, to be the upper-case equivalent for the purposes of comparison.

Sadly, the oldest version of sort that I can find is from 4.4BSD-lite2, which describes it the same as macOS does now, also without the word “fold” in sight. I’m guessing some older, more ancient documentation for proprietary Unix is floating around somewhere that describes the flags, too.

Amusingly, the option you’d think would be the case-insensitive comparison flag, -i, is instead “ignore all non-printable characters.” This is a great example of picking a precise-but-confusing name for something and getting stuck with it until the end of time.

Fixing slow Firefox loading when using Pi-Hole

I run Pi-Hole to prevent clients on my network from loading dangerous or gross things like advertisements or tracking scripts. This normally works great: it functions on all devices without requiring configuration and I don’t need to think about it too often.

I’ve also been using Firefox a lot more often lately, partly because Big Sur introduced a lot of Safari regressions and sites like Twitter are broken regularly, and partly because I like the features it’s coming out with like HTTPS-only mode.

Unfortunately, Firefox and Pi-Hole do not play nicely together when it comes to certain websites. For example, when loading sfchronicle.com several of its trackers have performance issues:

A graph of timings for a network request. The first is ‘Blocked’ with 5.11 seconds. After this, staggered after Blocked, is ‘DNS Resolution’ which shows a 1.01 second timing. The rest are 0 seconds: connecting, TLS setup, sending, waiting and receiving.

When Firefox comes across a host which resolves to 0.0.0.0, it appears to have some kind of internal retry mechanism that, combined with HTML’s sequential loading of scripts, causes a cascading set of delays making loading take an extremely long time.

Fortunately, Pi-Hole’s behavior of returning 0.0.0.0 for disallowed hosts is configurable. Changing its BLOCKINGMODE to NODATA changes the resolution behavior from:

$ dig +noall +question +answer secure.quantserve.com
  ;secure.quantserve.com.   IN  A
  secure.quantserve.com.  2 IN  A 0.0.0.0

to:

$ dig +noall +question +answer secure.quantserve.com
  ;secure.quantserve.com.   IN  A

Instead of providing an IP address, the response we get is instead that there are no A records for the domain, and Firefox gives up a lot faster, taking a few milliseconds instead of a few seconds. The Pi-Hole documentation on blocking modes provides a caveat:

…experiments suggest that clients may try to resolve blocked domains more often compared to NULL blocking…

The default blocking behavior (NULL) is returning 0.0.0.0. I have not come across any issues with this change, but I also don’t think I’d notice if DNS requests drastically increased on my network.

This doesn’t resolve all of the performance issues on the SF Chronicle in Firefox. Even using a non-Pi-Hole DNS server shows significant loading delays compared to Safari. This, at least, makes it painful instead of frustrating. As an aside, I am resentful that I’m paying $12 per month for a website that wants to inject the scummiest of Taboola-level ads on me.

First, from The Dragonbone Chair:

“Ko muhuhok na mik aqa nop, we say in Yiqanuc: ‘When it falls on your head, then you are knowing it is a rock.’”

“‘Mikmok hanno so gijiq,’ we say in Yiqanuc!” Binabik called. “‘If you wish to carry a hungry weasel in your pocket, it is your choice!”

A ponderous one from The Banished of Muirwood:

Your mind is your predicament. It wants to be free of change. Free of pain, free of the obligations of life and death. But change is law and no amount of pretending will alter that reality.

One that made me cry from Artemis:

“What’s up, Dad? You’re slow as snot today.”

“Just being thorough.”

“Are you kidding? I’ve seen you fire up a torch with one hand and set mixture levels with the other at the same time. Why are you—”

Oh. I stopped talking. This wasn’t a normal job. Tomorrow, his daughter’s life would rely on the quality of these welds. It slowly dawned on me that, to him, this was the most critical project he’d ever done. He would accept nothing short of his absolute best. And if that meant taking all day, so be it. I stood back and let him work. After more fastidious double checks, he got started. I assisted and did what I was told. We may have our friction, but when it came to welding he was the master and I was the apprentice. Very few people get a chance to quantify how much their father loves them. But I did. The job should have taken forty-five minutes, but Dad spent three and a half hours on it. My father loves me 366 percent more than he loves anything else. Good to know.

One that makes me ponder from City of Miracles:

What a tremendous sin impatience is, he thinks. It blinds us to the moment before us, and it is only when that moment has passed that we look back and see it was full of treasures.

One that made me laugh from Gardens of the Moon:

“It will be a fine day for a walk, pronounces Kruppe, who is wise in all things.”

– Kruppe (of course)

And finally one that reminds me enough of San Francisco that I couldn’t help but stop and think from The City Stained Red:

This city is sick, his father had once told him. It eats people and craps out gold and people pick the filth up off the city streets and shove it in their faces and smear it on their lips.

I created iosfontsizes.com, a quick way to glance at the dynamic type sizes Apple predefines. It’s an evolution of a Gist I created which I’ve been referring to for years, but now with the added benefit of an extremely memorable URL.

Robert Jackson Bennett, author of City of Stairs, wrote author notes for the book:

Every once in a while – mostly due to reader comments – I find myself wondering if the present tense is worth writing in. But the opening sequence to this chapter dispels any such thoughts from my mind.

and

Seriously, I forgot how fucking creepy Jukov is.

Standard Notes is an end-to-end encrypted notes syncing application built for longevity:

Our revolutionary, paradigm-shifting 21st-century business plan is to keep your information ready for the 22nd century. The notes you write now should be there for you in a 100 years. That’s our killer app.

It’s one of the rare times when I value function over form. It’s an ugly application, feeling wholly unnative and lacking in important things like keyboard shortcuts. Why keep using it? There’s no other service that can sync my notes with zero knowledge across devices.

One of the bigger holes–not being able to attach and sync files–is partially filled today with the release of FileSafe:

When you use FileSafe, you attach files[…]to your individual notes. These files are then encrypted by Standard Notes offline (client-side) first, then uploaded in their encrypted form to your Dropbox, Google Drive, or WebDAV compliant server (Nextcloud, ownCloud, Seafile, Synology, and others).

It’s barebones. You can upload to a note and download from a note. There’s no previews, no inlining. It doesn’t work on mobile yet. It’s not what you would expect out of attaching a file to a note.

Privacy is such a strong differentiator.

Another risk for generic TLDs: your registry may turn the TLD into a spam-filled mess which permanently ruins your domain.

This documentary/ad from AT&T in the 1970s has Bell Labs employees introduce and describe how Unix differs from other operating systems, as well as about the ethos of Unix.

Related is the Computerphile interviews with Brian Kernighan who participated in this era of Bell Labs (and this video quite excellently).

It’s impressive how they managed to build so much fundamental concepts and designs in such a short period of time. Much of the computing world is still based on how those original Unix programs were written and the decisions of those working in Bell Labs.

The Style Guide for Google’s open-source projects includes some interesting recommendations that I hadn’t seen before, including:

For file size optimization and scannability purposes, consider omitting optional tags. The HTML5 specification defines what tags can be omitted.

This includes tags like <html>, <head>, and <body> as well as closing tags for elements like </li> and </p>. The difference can be rather stark. An extremely basic page may look like:

<!DOCTYPE html>
<title>A page about nothing</title>
<h1>Introduction</h1>
<p>Also the conclusion.

Alignment rects in Auto Layout views

In the UIView documentation, Apple describes alignment rects:

The constraint-based layout system uses alignment rectangles to align views, rather than their frame. This allows custom views to be aligned based on the location of their content while still having a frame that encompasses any ornamentation they need to draw around their content, such as shadows or reflections.

At first glance, this feature feels unnecessary: views can draw outside their bounds seemingly without performance issues1. However, the differentiation between frame and alignment is a powerful, and easily overlooked, feature in Auto Layout.

Automatic with constraints

Views that use Auto Layout for positioning and sizing get alignment rect support for free via alignmentRectInsets. Unfortunately, going by the documentation, this is not evident2.

Let’s work through this using an example. The vertical line is going down the exact center of the container:

“XXX” label with a yellow box positioned to the right. The “XXX” is horizontally centered in the container, and the yellow box is positioned to the side of it

This view has a label and decorative yellow box to the side. Notice that we’re centering relative to the label, and not to the decorative box. This is accomplished with a very small amount of code:

override var alignmentRectInsets: UIEdgeInsets {
  return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
}

The constraints within the view can completely ignore this fact. With the insets set, the layoutMarginGuide and direct leading, trailing, etc., anchors are automatically inset. This is also true for constraints created via NSLayoutConstraint.

The entirety of the positioning for the view above looks like this:

label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
  label.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
  label.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
  label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
  label.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor)
])

// alignmentRectInsets doesn't support RTL,
// so use left/right rather than leading/trailing
yellowBox.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
  yellowBox.topAnchor.constraint(equalTo: topAnchor),
  yellowBox.bottomAnchor.constraint(equalTo: bottomAnchor),
  yellowBox.leftAnchor.constraint(equalTo: rightAnchor),
  yellowBox.widthAnchor.constraint(equalToConstant: alignmentRectInsets.right)
])

We can now apply whole-view effects, like the border in the example above, without having to create elaborate view hierarchies. The decoration is part of the view, not floating outside of it.

The only downside here is that alignmentRectInsets does not use the new NSDirectionalEdgeInsets introduced in iOS 11, so right-to-left support may need to look at effectiveUserInterfaceLayoutDirection.

Tappability

Apple recommends 44pt tappable areas for controls which are often designed to be aligned with other elements on the screen, often times closer than this tappable region.

To combat this problem, a common solution is overriding hitTest(_:with:) on the button to allowing it to tapped outside of its frame. This makes it hard to visualize where a screen is tappable when debugging the view.

Let’s solve this problem using alignment rects. A custom UIControl subclass can use the regular insets like the view example above. Unfortunately, UIButton does not use Auto Layout internally, so a small subclass is needed to handle the insets:

class IncreasedTappableButton: UIButton {
  // one potential optimization is to calculate insets that would
  // make the button >= 44.0 pt tall/wide
  override var alignmentRectInsets: UIEdgeInsets {
    return UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
  }

  override var intrinsicContentSize: CGSize {
    var size = super.intrinsicContentSize
    size.width += alignmentRectInsets.left + alignmentRectInsets.right
    size.height += alignmentRectInsets.top + alignmentRectInsets.bottom
    return size
  }

  private var boundsInsetByAlignmentRect: CGRect {
    return UIEdgeInsetsInsetRect(bounds, alignmentRectInsets)
  }

  override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
    return super.backgroundRect(forBounds: boundsInsetByAlignmentRect)
  }

  override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
    return super.imageRect(forContentRect: boundsInsetByAlignmentRect)
  }

  override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
    return super.titleRect(forContentRect: boundsInsetByAlignmentRect)
  }
}

This gives us a button where the frame is slightly bigger, but things are positioned relative to the original size:

A button which demonstrates a larger tappable area than the alignment rect

It is worth pointing out that this will not work inside a UIStackView since the larger frame will extend outside the bounds of its container and UIStackView is fairly aggressive about ignoring outside touches.

If outside views are opaque and do not cause blending or offscreen rendering, it does not appear there are any additional costs to having subviews drawing outside bounds.


  1. I’ve spent hours searching for documentation about the performance impact of drawing outside bounds, and there’s not a lot of detail available. ↩︎

  2. Often in Apple SDKs, the headers contain information the documentation is missing. This is from the UIView headers:

    Constraints do not actually relate the frames of the views, rather they relate the “alignment rects” of views. This is the same as the frame unless overridden by a subclass of UIView.

    I did not notice this and I am sure many others are missing this key piece of documentation. ↩︎

Doctors Without Borders uses drones to plan their operations by mapping people and infrastructure. What a smart use of of the technology. In places where accurate maps aren’t available, they generate them.

Better shadow performance on views

There are two different uses for the shadowPath property on CALayer:

  1. Improving the performance of having a shadow
  2. Creating shadows that don’t match the contents of the view. Check out Apple’s Using Shadow Path for Special Effects.

For performance reasons, always set a shadowPath. This is a substantial improvement, especially if the view changes position via animation or presence in a scroll view.

When you can set a path

The shadowPath tells the system what should be casting a shadow without having to look at the contents of the view itself. Since most views that need a shadow are opaque, we just need to describe the appearance of the background of the view.

Using the convenience initializers on UIBezierPath we can create ovals, squares and rounded rectangles without difficulty. For more complicated paths, check out A Primer on Bézier Curves. You can still use UIBezierPath or CGPath to create them, but it will require more complicated math.

Starting with a simple, purple view with a shadow:

let purpleView = UIView()
purpleView.backgroundColor = .purple
purpleView.layer.shadowRadius = 10.0
purpleView.layer.shadowColor = UIColor.black.cgColor
purpleView.layer.shadowOffset = CGSize()
purpleView.layer.shadowOpacity = 0.8

We can tell the system to draw a shadow for the entire square:

purpleView.layer.shadowPath = UIBezierPath(rect: purpleView.bounds).cgPath

A square view with a shadow

For rounded corners, we can set the cornerRadius property on the layer, and create a matching shadowPath:

purpleView.layer.cornerRadius = 16.0
purpleView.layer.shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 16.0).cgPath

A rounded rectangle view with a shadow

When you can’t set a path

Sometimes it’s not possible to set a path because there’s no easy way to describe the contents of the view. For example, text is a mess of random contents. Rasterizing the layer avoids having to draw the shadow repeatedly.

// create our label
let label = UILabel()
label.textColor = .purple
label.text = NSLocalizedString("Swift Lemma!", comment: "")
label.layer.shadowOpacity = 0.6
label.layer.shadowColor = UIColor.black.cgColor
label.layer.shadowOffset = CGSize(width: 0, height: 2)

// render and cache the layer
label.layer.shouldRasterize = true
// make sure the cache is retina (the default is 1.0)
label.layer.rasterizationScale = UIScreen.main.scale

This produces a view that looks like this:

“Swift Lemma!” with a shadow

Keep in mind

Always set the shadowPath inside either layoutSubviews() or viewDidLayoutSubviews(). Since Auto Layout likely means there aren’t constant sizes for views, setting a shadowPath elsewhere may become outdated or incorrect.

When creating a path, the coordinate system for the path is the layer it’s applied to. To make it easier, pretend the shadow path is a subview. For this reason, we use the bounds of the view to create its shadow path.

Layout margins within a UIStackView

The UIStackView property isLayoutMarginsRelativeArrangement allows insets similar to margin constraints on subviews in a UIView.

Let’s consider a simple single-subview example:

let containedView = UIView()
containedView.backgroundColor = .purple

let stackView = UIStackView()
stackView.addArrangedSubview(containedView)

You can then configure directionalLayoutMargins and enable them like so:

stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(
    top: 8,
    leading: 8,
    bottom: 8,
    trailing: 8
)
stackView.isLayoutMarginsRelativeArrangement = true

This is what it looks like, before and after:

The subview stretches from edge to edge without margins The subview is inset on all edges by 8 points

ICANN created a history project documenting its formation nineteen years ago. I’m captivated by the interviews: corralling support and preventing disputes between so many interests seems like an impossible task. I couldn’t imagine the current political climate would come close to breaking something away from the US government with such bipartisan support. Their policies are downright ridiculous at times, but their history is certainly rich.

Is using a generic top-level domain a good idea?

I’ve been thinking about switching over my website and email to one of the new top-level domains. This has lead me to investigating what the switch would feel like, and how stable the move would be.

Will it survive?

I am looking at the .engineer gTLD now owned by Donuts. At the time of writing this, there are a total of 2706 registered domains since late 2014. That’s nothing.

That got me thinking: what exactly happens when a gTLD fails?

The answers aren’t clear. When applying, ICANN requires registrars put up cash in the form of a bond to cover operational costs for 3 years. If a registrar were to fail, another can propose to take over. Their database is stored off-site, and data can be migrated.

But what if nobody does? What happens to a gTLD if there’s not enough domains to stay in business? The answer, it seems, is that the domain ends. There’s no provisions at ICANN to maintain domains beyond the transfer procedure.

Donuts, for what it’s worth, has stated they would not shut any down:

We think of all the TLDs as one big registry. It[’]s profitable, so all our TLDs are profitable, but that is beside the point. We’d no more shut down one of our TLDs than you would shut down 100 “unprofitable” second-level names in .link.

There’s definitely risk, and that’s not what the internet needs. It should be that, regardless of the fate of any registrar, a domain you purchase today will be valid as long as you renew it.

As an email

Generic top-level domains have been available for registration since 2013, but there’s a number of services that can’t handle them. I’m surprised how many times I enter one into an email field and see “invalid address” as the result.

The responses I’ve received are generally the “doing it wrong” variety and not the “I’ve filed an issue and we’ll look into it.” I’m not sure what I expected to be honest; I hoped that it would be passed up the food chain, but it always dies in the first round of support.

This means, to use a gTLD, I need to keep a backup domain for services like AT&T, CBS, Virgin Airlines, and Crunchyroll. I expected that in 2017 it wouldn’t be a problem, and for the most part it isn’t an issue. It’s frustrating though.

Premium domains

New.net tried replacing ICANN’s authority in the past, long before gTLDs existed. They offered some snazzy options, and I grabbed zac.tech to play around. It didn’t work on most ISPs, but it did work on mine.

That’s a valid gTLD now! I could register it again! For the low, low cost of $2800. Per year.

This notion of a premium domain name is a money-grab by registrars. What constitutes a “premium” domain is arbitrary: length, dictionary words, prettiness, etc. If you try to register one of these domains at NearlyFreeSpeech you get a perfectly correct error:

This means the registry of this gTLD plans to extort extra money from anyone who wants this domain.

It is, and they do. These premium prices may come down. Perhaps they’ll stop charging extra to renew them entirely. But when your registry has a few thousand total domains are premium bottlenecks the right way to go about this?

The future?

I’m worried that entire namespaces are being taken by companies for their internal use, like Google seems to be doing with .dev. If you’ve got the cash, you can take complete, even dictatorial, ownership. That’s not how existing domains worked, but it’s the rules we’re living under with ICANN’s leadership.

But we can’t continue to have one namespace. We’ve been in a world where everything but .com was wrong, and Verisign’s control over it has been harsh. These new top-level domains are nicer looking and there’s significantly more availability.

So I’m thinking about switching. There’s a lot to choose from, and more opening up every day. I’m on a ccTLD right now, and there’s a real risk that it could go away at any time through local laws or disputes. Remember when every startup was using Libya’s .ly domain?

Generic top-level domains feel like an improvement for the internet as a whole. The cruft at the end doesn’t have to be cruft; it can be descriptive, it can be helpful, and above all it can be nice.

Libby is a nice way to browse the San Francisco Public Library’s collection of eBooks and audiobooks. It has a lot of rough edges. It makes up for it with the send-to-Kindle feature so I can read borrowed books on a proper screen.

How Apple explains audio session prioritization, from Activating an Audio Session:

Comic

I did not expect a comic when I clicked on the documentation link.

In Apple’s latest The Rock x Siri advertisement you can hear the Wilhelm Scream at 2m43s. After the episode of Twenty Thousand Hertz about its backstory, it’s hard not to find it everywhere.

Cover

Age of Swords comes out next week, but that didn’t stop this Barnes & Noble from putting it out early. If I were willing to go non-digital I’d be reading it right now.

Jeff Rosenstock’s WORRY. came out last October. Since then, I’ve listened to it dozens of times. I can’t think of an artist that I’ve listened to more over the years. His writing and music style just blows me away.