One of the great unsolved questions in iOS development is,“How do I store secrets securely on the client?”
Baked into this question is the assumption that,
without taking proper measures,
those secrets will be leaked in one way or another —
either in source revision control (GitHub and the like),
from analysis tools running on .ipa files distributed through the App Store,
or some other way.
Although credential security is often a second-tier concern at best,
there’s a large and growing body of academic work
that compels us, as developers, to take these threats seriously.
For instance,
researchers at North Carolina State Universityfound
that thousands of API keys, multi-factor secrets, and other sensitive credentials
are leaked every day on GitHub.(Meli, McNiece, & Reaves, 2019)
Another paper published in 2018found
SDK credential misuse in 68 out of a sample of 100 popular iOS apps.(Wen, Li, Zhang, & Gu, 2018)
This week on NSHipster,
we’re shamelessly invoking
a meme from 2017
to disclose progressively more sophisticated countermeasures
for keeping your secrets safe.
If your app is among the myriad others out there with an embedded
Twitter access token,
Stripe API key,
AWS Key ID,
or any other manner of credential,
read on and expand your mind.
🧠 Normal Brain Hard-Code Secrets in Source Code
Imagine an app that communicates with a web application
that requires an API key (a secret)
to be attached to outbound requests
for purposes of authentication.
This integration could be managed entirely by a third-party framework,
or it might be brokered with a URLSession you set up in AppDelegate.
In either case,
there’s a question of how and where to store the required API key.
Let’s consider the consequences of embedding the secret in code
as a string literal:
When you archive your app for distribution,
all traces of this code appear to vanish.
The act of compilation transforms
human-readable text into machine-executable binary,
leaving no trace of the secret… right?
Not quite.
Using a reverse-engineering tool
like Radare2,
that secret is plainly visible from inside the compiled executable.
You can prove this to yourself with your own project by
selecting “Generic iOS Device” in the active scheme in your own project,
creating an archive (Product > Archive),
and inspecting the resulting .xcarchive bundle:
While a typical user downloading your app from the App Store
would never even think to try opening the .ipa on their device,
there are plenty of individuals and organizations whomst’d’ve
carved out a niche in the mobile app industry
analyzing app payloads for analytics and security purposes.
And even if the majority of these firms are above-board,
there’s likely to be a few bots out there
combing through apps just to pick out hard-coded credentials.
Much of that is just conjecture,
but here’s what we know for sure:
If you hard-code secrets in source code,
they live forever in source control —
and forever’s a long time when it comes to keeping a secret.
All it takes is one misconfiguration to your repo permissions
or a data breach with your service provider
for everything to be out in the open.
And as one human working with many other humans,
chances are good that someone, at some point, will eventually mess up.
Not to put too fine a point on it, but:
To quote Benjamin Franklin:
If you would keep your secret from an enemy, tell it not to a friend.
🧠 Big Brain Store Secrets in Xcode Configuration and Info.plist
So long as these files are kept out of source control,
this approach solves the problem of leaking credentials in code.
And at first glance,
it would appear to remediate our concern about leaking to static analysis:
If we fire up our l33t hax0r tools
and attenuate them to our executable as before
(izz to list strings binary,~6a0f07 to filter using the first few characters of our secret),
our search comes up empty:
That Info.plist file we used to store our secrets?
Here it is, packaged right alongside our executable.
By some measures,
this approach might be considered less secure than writing secrets in code,
because the secret is accessible directly from the payload,
without any fancy tooling.
There doesn’t seem to be a way to configure the environment
when a user launches your app on their device.
And as we saw in the previous section,
using Info.plist as a clearinghouse for secrets qua build settings
is also a no-go.
But maybe there’s another way we could capture our environment in code…
🧠 Cosmic Brain Obfuscate Secrets Using Code Generation
A while back,
we wrote about GYB,
a code generation tool used in the Swift standard library.
Although that article focuses on eliminating boilerplate code,
GYB’s metaprogramming capabilities go far beyond that.
For example,
we can use GYB to pull environment variables into generated code:
$API_KEY=6a0f0731d84afa4082031e3a72354991 \
gyb --line-directive''<<"EOF"%{ import os }%let apiKey = "${os.environ.get('API_KEY')}"EOF
let apiKey = "6a0f0731d84afa4082031e3a72354991"
Generating (and not committing) Swift files from GYB while
pulling secrets from environment variables
solves the problem of leaking credentials in source code,
but it fails to guard against common static analysis tools.
However,
we can use a combination of Swift and Python code (via GYB)
to obfuscate secrets in a way that’s more difficult to reverse-engineer.
For example, here’s an (admittedly crude) solution
that implements an XOR cipher
using a salt that’s generated randomly each time:
Secrets are pulled from the environment and encoded by a Python function
before being included in the source code as [UInt8] array literals.
Those encoded values are then run through an equivalent Swift function
to retrieve the original value
without exposing any secrets directly in the source.
You don’t have to outrun a bear to be safe.
You just have to outrun the guy next to you.
🧠 Galaxy Brain Don’t Store Secrets On-Device
No matter how much we obfuscate a secret on the client,
it’s only a matter of time before the secret gets out.
Given enough time and sufficient motivation,
an attacker will be able to reverse-engineer
whatever you throw their way.
The only true way to keep secrets in mobile apps
is to store them on the server.
Following this tack,
our imagined plot shifts from an Oceans 11-style heist movie
to a high-stakes Behind Enemy Lines escort mission movie.(Your mission:
Transfer a payload from the server
and store it in theSecure Enclave
without an attacker compromising it.)
If you don’t have a server that you can trust,
there are a few Apple services that can serve as a transport mechanism:
CloudKit Database:
Set secrets in a private database in your CloudKit Dashboard.(Bonus: Subscribe to changes for automatic credential rolling functionality)
Background Updates:
Push secrets silently to clients as soon as they come online viaAPNS.
Then again,
once your secret reaches the Secure Enclave,
it’ll be sent right back out with the next outbound request
for which it’s used.
It’s not enough to get it right once;
security is a practice.
Or put another way:
A secret in Secure Enclave is safe,
but that is not what secrets are for.
🧠 Universe Brain Client Secrecy is Impossible
There’s no way to secure secrets stored on the client.
Once someone can run your software on their own device,
it’s game over.
And maintaining a secure, closed communications channel between client and server
incurs an immense amount of operational complexity —
assuming it’s possible in the first place.
Perhaps Julian Assange said it best:
The only way to keep a secret is to never have one.
Rather than looking at client secret management as a problem to be solved,
we should see it instead as an anti-pattern to be avoided.
What is an API_KEY other than an insecure, anonymous authentication mechanism, anyway?
It’s a blank check that anyone can cash,
a persistent liability the operational integrity of your business.
Any third-party SDK that’s configured with a client secret is insecure by design.
If your app uses any SDKs that fits this description,
you should see if it’s possible to move the integration to the server.
Barring that,
you should take time to understand the impact of a potential leak
and consider whether you’re willing to accept that risk.
If you deem the risk to be substantial,
it wouldn’t be a bad idea to look into ways to obfuscate sensitive information
to reduce the likelihood of exposure.
Restating our original question:“How do I store secrets securely on the client?”
Our answer:“Don’t (but if you must, obfuscation wouldn’t hurt).”
Cosmologies seek to create order
by dividing existence into discrete, interdependent parts.
Thinkers in every society throughout history
have posited various arrangements —
though Natural numbers
being what they are,
there are only so many ways to slice the ontological pie.
There are dichotomies like陰陽:
incontrovertible and self-evident (albeit reductive).
There are trinities,
which position man in relation to heaven and earth.
One might divide everything into four,like the ancient Greeks
with the elements of
earth, water, air, and fire.
Or you could carve things into five,like the Chinese
with木,火,土,金,
and水.
Still not satisfied?
Perhaps the eight-part八卦
will provide the answers that you seek:
Trigram
☰
☱
☲
☳
☴
☵
☶
☷
Nature
天
澤
火
雷
風
水
山
地
Despite whatever galaxy brain opinion we may have about computer science,
the pragmatic philosophy of day-to-day programming
more closely aligns with a mundane cosmology;
less imago universi, moreじゃんけん.
For a moment, ponder the mystical truths of fundamental Swift collection types:
Arrays are ordered collections of values.
Sets are unordered collections of unique values.
Dictionaries are unordered collections of key-value associations.The Book of Swift
Thus compared to the pantheon ofjava.util collections
or std containers,
Swift offers a coherent coalition of three.
Yet,
just as we no longer explain everyday phenomena strictly in terms ofhumors
or æther,
we must reject this formulation.
Such a model is incomplete.
We could stretch our understanding of sets to incorporateOptionSet (as explained in a previous article),
but we’d be remiss to try and shoehorn Range and ClosedRange
into the same bucket as Array—
and that’s to say nothing of the panoply of
Swift Collection Protocols(an article in dire need of revision).
This week on NSHipster,
we’ll take a look at KeyValuePairs,
a small collection type
that challenges our fundamental distinctions betweenArray, Set, and Dictionary.
In the process,
we’ll gain a new appreciation and a deeper understanding
of the way things work in Swift.
KeyValuePairs is a structure in the Swift standard library that —
surprise, surprise—
represents a collection of key-value pairs.
This truncated declaration highlights the defining features of KeyValuePairs:
Its ability to be expressed by a dictionary literal
Its capabilities as a random-access collection
Dictionary Literals
Literals
allow us to represent values directly in source code,
and Swift is rather unique among other languages
by extending this functionality to our own custom types through protocols.
A dictionary literal
represents a value as mapping of keys and values like so:
["key":"value"]
However, the term“dictionary literal” is a slight misnomer,
since a sequence of key-value pairs — not a Dictionary—
are passed to the ExpressibleByDictionaryLiteral protocol’s required initializer:
This confusion was amplified by the existence of a DictionaryLiteral type,
which was only recently renamed to KeyValuePairs in Swift 5.
The name change served to both clarify its true nature
and bolster use as a public API
(and not some internal language construct).
You can create a KeyValuesPairs object
with a dictionary literal
(in fact, this is the only way to create one):
KeyValuePairs conforms to RandomAccessCollection,
which allows its contents to be retrieved by (in this case, Int) indices.
In contrast to Array,KeyValuePairs doesn’t conform to RangeReplaceableCollection,
so you can’t append elements of remove individual elements at indices or ranges.
This narrowly constrains KeyValuePairs,
such that it’s effectively immutable once initialized from a dictionary literal.
These functional limitations are the key to understanding
its narrow application in the standard library.
KeyValuePairs in the Wild
Across the Swift standard library and Apple SDK,KeyValuePairs are found in just two places:
@dynamicCallablestructKeywordCallable{funcdynamicallyCall(withKeywordArgumentsargs:KeyValuePairs<String,Int>)->Int{returnargs.count}}letobject=KeywordCallable()object(a:1,2)// desugars to `object.dynamicallyCall(withKeywordArguments: ["a": 1, "": 2])`
On both occasions,KeyValuePairs is employed as an alternative to [(Key, Value)]
to enforce restraint by the caller.
Without any other public initializers,KeyValuePairs can only be constructed from dictionary literals,
and can’t be constructed dynamically.
Working with KeyValuePairs Values
If you want to do any kind of work with a KeyValuePairs,
you’ll first want to convert it into a conventional Collection type —
either Array or Dictionary.
Converting to Arrays
KeyValuePairs is a Sequence,
by virtue of its conformance to RandomAccessCollection
(and therefore Collection).
When we pass it to the corresponding Array initializer,
it becomes an array of its associated Element type ((Key, Value)).
letarrayOfPairs:[(Key,Value)]=Array(pairs)
Though,
if you just want to iterate over each key-value pair,
it’s conformance to Sequence means that you can pass it directly to a for-in loop:
for(key,value)inpairs{…}
You can always create an Array from a KeyValuePairs object,
but creating Dictionary is more complicated.
Converting to Dictionaries
There are four built-in types that conform to ExpressibleByDictionaryLiteral:
Dictionary
NSDictionary
NSMutableDictionary
KeyValuePairs
Each of the three dictionary types constitutes asurjective mapping,
such that every value element has one or more corresponding keys.KeyValuePairs is the odd one out:
it instead maintains an ordered list of tuples
that allows for duplicate key associations.
Dictionary got a number of convenient initializers in Swift 4
thanks to SE-0165(thanks, Nate!),
includinginit(uniqueKeysWithValues:),init(_:uniquingKeysWith:), andinit(grouping:by)
Consider the following example that
constructs a KeyValuePairs object with a duplicate key:
Outside of its narrow application in the standard library,KeyValuePairs are unlikely to make an appearance in your own codebase.
You’re almost always better off going with a simple [(Key, Value)] tuple array.
However,
the very existence of
Like all cosmological exceptions,KeyValuePairs is uncomfortable — at times, even unwelcome —
but it serves to expand our understanding.
Much as today’s Standard Model
more closely resembles
the cacophony of azoo
than themusica universalis ofcelestial spheres,KeyValuePairs
challenges our tripartite view of Swift collection types.
But like all cosmological exceptions —
though uncomfortable or even unwelcome at times —
it serves to expand our understanding.
This Thursday is Thanksgiving in the United States,
a holiday in which we’ll gather together to cultivate and express gratitude
against a backdrop of turkey, mashed potatoes, and
origin-myths of European colonialism in the Americas.
As the year —
and indeed the decade—
draws to a close,
we’re invited to reflect on the blessings in our lives:
The individuals who fill them with joy,
the circumstances that bring them comfort and security,
the pursuits that endow them with meaning.
…of course, it’s also a time of ritualized, over-indulgent consumption.
And for the PC gamers among us,
that means shutting down our Macs,
booting into our Windows partitions,
and settling into our long-neglected back catalogs on Steam.
So while you wait for your AAA titles to download
and your Windows software updates to apply(let’s assume you’re reading this on your iPhone),
we invite you to count your blessings.
This week on NSHipster:
a quick look at the bless command and the macOS boot process.
But first, a quick disclaimer:
The process of booting a computer is a small miracle.
Starting with nothing —
“tabula rasa”—
a computer incrementally runs smaller, simpler programs
to load larger, more capable programs into memory,
until it finally has everything it needs to
run the operating system itself.
In a previous era of computers,
operating systems were initialized usingBIOS firmware,
which came pre-installed in systemROM.
When a machine powered up,
that firmware would be the first thing that the computer encountered,
and it’d proceed to load machine code from the boot sector.
Nowadays,
Macs and most other computers boot according to theExtensible Firmware Interface
(EFI),
which introduces a few levels of indirection for greater flexibility.
When a machine powers on under this regime,
it loads a boot manager,
which is responsible for loading configuration fromnon-volatile RAM
(NVRAM),
including the file system paths of the
loaders and kernels for the operating system.
This additional step allows computers to
boot into different partitions on disk
and even network volumes.
The process of making a volume bootable is called blessing,
and the operative command to accomplish this has been, historically,bless.
To get an overview of the blessed volumes on your machine,
run bless --info:
$sudo bless --info… => Blessed System File is {Preboot}/…/System/Library/CoreServices/boot.efi… => Blessed System Folder is {Preboot}/…/System/Library/CoreServicesThe blessed volume in this APFS container is "/".
No blessed APFS snapshot for this volume.
Blessing a volume on the latest Mac hardware running the latest version of macOS
is an involved process,
which includes
(among other things):
Copying boot.efi to the correct file system path on the volume
Writing information to the volume header to designate it as being bootable
Setting the efi-boot-device and efi-boot-device-data variables
to designate the volume as the next startup disk
To illustrate how much complexity is baked into blessing a volume,
let’s focus on the first of these tasks:
Copy boot.efi? Easy: cp boot.efi "/Volumes/Macintosh HD".
(It’s a UNIX system! I know this!)
Well, not quite.
On a Mac booted from an APFS disk,boot.efi is in a special preboot volume (/dev/disk1s2),
at a path containing a generated UUID.HFS+ is rather simpler by comparison,
since the boot loader has a hard-coded located at/System/Library/CoreServices/boot.efi.
That is, unless,
the disk is encrypted with FileVault full-disk encryption
or is part of a RAID array that requires additional drivers.
And all of this is to say nothing of Secure Boot
and the Apple T2 Security Chip,
which comes standard on the iMac Pro as well as
Mac Mini, MacBook Air, and MacBook Pro computers since 2018.
But in case you remain undeterred,
and really want to avoid the inconvenience of opening up System Preferences,
here’s the invocation you seek:
$sudo bless -mount /Volumes/BOOTCAMP -setBoot--nextonlyCould not set boot device property: 0xe00002e2
A suggested workaround invokes systemsetup
as a replacement for bless.
The systemsetup command is similar to System Preferences,
in that it can be used to set machine-wide preferences,
including:
date and time settings,
remote login and Apple Events and
the behavior of the physical power button,
as well as when a computer should go to sleep and
what should happen after a power failure.
The systemsetup invocation to set the startup disk
goes a little something like this:
$sudo systemsetup -setstartupdisk /Volume/BOOTCAMP/WindowsSetting startup disk not allowed while System Integrity Protection is enabled.
But that, too,
doesn’t seem to work on recent versions of macOS
without disabling SIP(and for the vast majority of folks,
there’s never a good reason to do this).
So by now,
Windows has finished updating itself (maybe),
the game is 100% downloaded and ready to launch,
and you’re left wondering
“What’s the point of this article?”
To that, dear reader,
I’d first say that there are far, far worse things than being pointless(or Point-Free, as it were).
But if you’re still searching for a takeaway,
here it is:
You probably won’t ever have an occasion to use the bless command(or systemsetup, for that matter).
With every design decision,
software vendors perform a balancing act between security and convenience.
For most people,
the new security features in recent versions of macOS and new Apple hardware
make them undeniably safer,
usually without any perceivable downside.
Yet we, as developers,
especially those of us with a hacker mentality,
may see the Mac’s trajectory towards a closed, iOS-like security model
as a mixed blessing —
a curse, even.
If that’s the case for you
(and even if it isn’t),
then please allow us to offer another helping of article-concluding takeaway:
Take time to be thankful for all that you have,
and seek to be present for those around you this holiday season.
Though we’re a long way off from
Hal orHer,
we shouldn’t forget about the billions of people out there for us to talk to.
Of the thousands of languages in existence,
an individual is fortunate to gain a command of just a few within their lifetime.
And yet,
over several millennia of human co-existence,
civilization has managed to make things work (more or less)
through an ad-hoc network of
interpreters, translators, scholars,
and children raised in the mixed linguistic traditions of their parents.
We’ve seen that mutual understanding fosters peace
and that conversely,
mutual unintelligibility destabilizes human relations.
It’s fitting that the development of computational linguistics
should coincide with the emergence of the international community we have today.
Working towards mutual understanding,
intergovernmental organizations like the United Nations and European Union
have produced a substantial corpus of
parallel texts,
which form the foundation of modern language translation technologies.
Computer-assisted communication
between speakers of different languages consists of three tasks:transcribing the spoken words into text,translating the text into the target language,
and synthesizing speech for the translated text.
This article focuses on how iOS handles the last of these: speech synthesis.
Introduced in iOS 7 and available in macOS 10.14 Mojave,AVSpeechSynthesizer produces speech from text.
To use it,
create an AVSpeechUtterance object with the text to be spoken
and pass it to the speakUtterance(_:) method:
Mac OS 9 users will no doubt have fond memories of the old system voices —
especially the novelty ones, like
Bubbles, Cellos, Pipe Organ, and Bad News.
In the spirit of quality over quantity,
each language is provided a voice for each major locale region.
So instead of asking for “Fred” or “Markus”,
AVSpeechSynthesisVoice asks for en-US or de-DE.
VoiceOver supports over 30 different languages.
For an up-to-date list of what’s available,
call AVSpeechSynthesisVoice class method speechVoices()
or check this support article.
By default,AVSpeechSynthesizer will speak using a voice
based on the user’s current language preferences.
To avoid sounding like a
stereotypical American in Paris,
set an explicit language by selecting a AVSpeechSynthesisVoice.
Many APIs in foundation and other system frameworks
use ISO 681 codes to identify languages.AVSpeechSynthesisVoice, however, takes anIETF Language Tag,
as specified BCP 47 Document Series.
If an utterance string and voice aren’t in the same language,
speech synthesis fails.
Not all languages are preloaded on the device,
and may have to be downloaded in the background
before speech can be synthesized.
Customizing Pronunciation
A few years after it first debuted on iOS,AVUtterance added functionality to control
the pronunciation of particular words,
which is especially helpful for proper names.
To take advantage of it,
construct an utterance using init(attributedString:)
instead of init(string:).
The initializer scans through the attributed string
for any values associated with the AVSpeechSynthesisIPANotationAttribute,
and adjusts pronunciation accordingly.
importAVFoundationlettext="It's pronounced 'tomato'"letmutableAttributedString=NSMutableAttributedString(string:text)letrange=NSString(string:text).range(of:"tomato")letpronunciationKey=NSAttributedString.Key(rawValue:AVSpeechSynthesisIPANotationAttribute)// en-US pronunciation is /tə.ˈme͡ɪ.do͡ʊ/mutableAttributedString.setAttributes([pronunciationKey:"tə.ˈme͡ɪ.do͡ʊ"],range:range)letutterance=AVSpeechUtterance(attributedString:mutableAttributedString)// en-GB pronunciation is /tə.ˈmɑ.to͡ʊ/... but too bad!utterance.voice=AVSpeechSynthesisVoice(language:"en-GB")letsynthesizer=AVSpeechSynthesizer()synthesizer.speak(utterance)
To get IPA notation that AVSpeechUtterance can understand,
you can open the Settings app,
navigate to Accessibility > VoiceOver > Speech > Pronunciations,
and… say it yourself!
Hooking Into Speech Events
One of the coolest features of AVSpeechSynthesizer
is how it lets developers hook into speech events.
An object conforming to AVSpeechSynthesizerDelegate can be called
when a speech synthesizer
starts or finishes,
pauses or continues,
and as each range of the utterance is spoken.
For example, an app —
in addition to synthesizing a voice utterance —
could show that utterance in a label,
and highlight the word currently being spoken:
#pragma mark - AVSpeechSynthesizerDelegate-(void)speechSynthesizer:(AVSpeechSynthesizer*)synthesizerwillSpeakRangeOfSpeechString:(NSRange)characterRangeutterance:(AVSpeechUtterance*)utterance{NSMutableAttributedString*mutableAttributedString=[[NSMutableAttributedStringalloc]initWithString:utterance.speechString];[mutableAttributedStringaddAttribute:NSForegroundColorAttributeNamevalue:[UIColorredColor]range:characterRange];self.utteranceLabel.attributedText=mutableAttributedString;}
Check out this Playground
for an example of live text-highlighting for all of the supported languages.
Anyone who travels to an unfamiliar place
returns with a profound understanding of what it means to communicate.
It’s totally different from how one is taught a language in High School:
instead of genders and cases,
it’s about emotions
and patience
and clinging onto every shred of understanding.
One is astounded by the extent to which two humans
can communicate with hand gestures and facial expressions.
One is also humbled by how frustrating it can be when pantomiming breaks down.
In our modern age, we have the opportunity to go out in a world
augmented by a collective computational infrastructure.
Armed with AVSpeechSynthesizer
and myriad other linguistic technologies on our devices,
we’ve never been more capable of breaking down the forces
that most divide our species.
If that isn’t universe-denting,
then I don’t know what is.
Code exists in a world of infinite abundance.
Whatever you can imagine is willed into being —
so long as you know how to express your desires.
As developers,
we know that code will eventually be compiled into software,
and forced to compete in the real-world for
allocation of scarce hardware resources.
Though up until that point,
we can luxuriate in the feeling of unbounded idealism…
well, mostly.
For software is not a pure science,
and our job — in reality —
is little more than
shuttling data through broken pipes between leaky abstractions.
This week on NSHipster,
we’re exploring a quintessential aspect of our unglamorous job:
API availability.
The good news is that Swift provides first-class constructs
for dealing with these real-world constraints
by way of @available and #available.
However,
there are a few nuances to these language features,
of which many Swift developers are unaware.
So be sure to read on
to make sure that you’re clear on all the options available to you.
@available
In Swift,
you use the @available attribute
to annotate APIs with availability information,
such as“this API is deprecated in macOS 10.15” or
“this API requires Swift 5.1 or higher”.
With this information,
the compiler can ensure that any such APIs used by your app
are available to all platforms supported by the current target.
The @available attribute can be applied to declarations,
including
top-level functions, constants, and variables,
types like structures, classes, enumerations, and protocols,
and type members —
initializers, class deinitializers, methods, properties, and subscripts.
Platform Availability
When used to designate platform availability for an API,
the @available attribute can take one or two forms:
A “shorthand specification”
that lists minimum version requirements for multiple platforms
An extended specification
that can communicate additional details about
availability for a single platform
Shorthand Specification
@available(platformversion, platform version ..., *)
A platform;iOS,macCatalyst,macOS / OSX,tvOS, orwatchOS,
or any of those with ApplicationExtension appended(e.g. macOSApplicationExtension).
A version number
consisting of one, two, or three positive integers,
separated by a period (.),
to denote the major, minor, and patch version.
Zero or more versioned platforms in a comma-delimited (,) list.
An asterisk (*),
denoting that the API is unavailable for all other platforms.
An asterisk is always required for platform availability annotations
to handle potential future platforms
(such as the long-rumored iDishwasherOS).
For example,
new, cross-platform APIs introduced at WWDC 2019
might be annotated as:
Shorthand specifications are a convenient way
to annotate platform availability.
But if you need to communicate more information,
such as when or why an API was deprecated
or what should be used as a replacement,
you’ll want to opt for an extended specification instead.
Introduced, Deprecated, Obsoleted, and Unavailable
A platform, same as before,
or an asterisk (*) for all platforms.
Either introduced, deprecated, and/or obsoleted…
An introducedversion,
denoting the first version in which the API is available
A deprecatedversion,
denoting the first version when using the API
generates a compiler warning
An obsoletedversion,
denoting the first version when using the API
generates a compiler error
…or unavailable,
which causes the API to generate a compiler error when used
renamed with a keypath to another API;
when provided, Xcode provides an automatic “fix-it”
A message string to be included in the compiler warning or error
Unlike shorthand specifications,
this form allows for only one platform to be specified.
So if you want to annotate availability for multiple platforms,
you’ll need stack @available attributes.
For example,
here’s how the previous shorthand example
can be expressed in multiple attributes:
Apple SDK frameworks make extensive use of availability annotations
to designate new and deprecated APIs
with each release of iOS, macOS, and other platforms.
For example,
iOS 13 introduces a newUICollectionViewCompositionalLayout class.
If you jump to its declaration
by holding command (⌘) and clicking on that symbol in Xcode,
you’ll see that it’s annotated with @available:
This @available attribute tells the compiler thatUICollectionViewCompositionalLayout can only be called
on devices running iOS, version 13.0 or later(with caveats; see note below).
If your app targets iOS 13 only,
then you can use UICollectionViewCompositionalLayout
without any special consideration.
If, however,
your deployment target is set below iOS 13
in order to support previous versions of iOS
(as is the case for many existing apps),
then any use of UICollectionViewCompositionalLayout
must be conditionalized.
More on that in a moment.
Swift Language Availability
Your code may depend on a new language feature of Swift,
such as property wrappers
or default enumeration case associated values—
both new in Swift 5.1.
If you want to support development of your app with previous versions of Xcode
or for your Swift package to work for multiple Swift compiler toolchains,
you can use the @available attribute
to annotate declarations containing new language features.
When used to designate Swift language availability for an API,
the @available attribute takes the following form:
@available(swift version)
Unlike platform availability,
Swift language version annotations
don’t require an asterisk (*);
to the compiler, there’s one Swift language,
with multiple versions.
#available
In Swift,
you can predicate if, guard, and while statements
with an availability condition,#available,
to determine the availability of APIs at runtime.
Unlike the @available attribute,
an #available condition can’t be used for Swift language version checks.
The syntax of an #available expression
resembles that of an @available attribute:
if | guard | while#available(platformversion, platform version ..., *)…
Now that we know how APIs are annotated for availability,
let’s look at how to annotate and conditionalize code
based on our platform and/or Swift language version.
When you attempt to call an API that is unavailable
for at least one of your supported targets,
Xcode will recommend the following options:
“Add if #available version check”
“Add @available attribute to enclosing declaration”
(suggested at each enclosing scope; for example,
the current method as well as that method’s containing class)
Following our analogy to error handling,
the first option is similar to prepending try to a function call,
and the second option is akin to wrapping a statement within do/catch.
For example,
within an app supporting iOS 12 and iOS 13,
a class that subclasses UICollectionViewCompositionalLayout
must have its declaration annotated with @available,
and any references to that subclass
would need to be conditionalized with #available:
Swift comprises many inter-dependent concepts,
and crosscutting concerns like availability
often result in significant complexity
as various parts of the language interact.
For instance,
what happens if you create a subclass that overrides
a property marked as unavailable by its superclass?
Or what if you try to call a function that’s renamed on one platform,
but replaced by an operator on another?
While it’d be tedious to enumerate every specific behavior here,
these questions can and often do arise
in the messy business of developing apps in the real world.
If you find yourself wondering “what if”
and tire of trial-and-error experimentation,
you might instead try consulting the
Swift language test suite
to determine what’s expected behavior.
‘unavailable’ availability overrides all other availability information
warning:
unknown platformAin availability macro
warning:
feature cannot be
introduced
deprecated
obsoleted
inBversionCbefore it was
introduced
deprecated
obsoleted
in versionE; attribute ignored
warning:
use same version number separators ‘_’ or ‘.’; as in ‘major[.minor[.subminor]]’
warning:
availability does not match previous declaration
warning:
overridding
method
introduced after
deprecated before
obsoleted before
the protocol method it implements
overridden method
onB (Cvs.D)
warning:
overridding
method cannot be unavailable onAwhen
the protocol method it implements
its overridden method
is available
Annotating Availability in Your Own APIs
Although you’ll frequently interact with @available
as a consumer of Apple APIs,
you’re much less likely to use them as an API producer.
Availability in Apps
Within the context of an app,
it may be convenient to use @available deprecation warnings
to communicate across a team
that use of a view controller, convenience method, or what have you
is no longer advisable.
Use of unavailable or deprecated, however,
are much less useful for apps;
without any expectation to vend an API outside that context,
you can simply remove an API outright.
Availability in Third-Party Frameworks
If you maintain a framework that depends on the Apple SDK in some way,
you may need to annotate your APIs according
to the availability of the underlying system calls.
For example,
a convenience wrapper aroundKeychain APIs
would likely annotate the availability of
platform-specific biometric features like Touch ID and Face ID.
However,
if your APIs wrap the underlying system call
in a way that doesn’t expose the implementation details,
you may be able
For example,
an NLP
library that previously delegated functionality toNSLinguisticTagger
could instead useNatural Language framework
when available
(as determined by #available),
without any user-visible API changes.
Availability in Swift Packages
If you’re writing Swift qua Swift
in a platform-agnostic way
and distributing that code as a Swift package,
you may want to use @available
to give a heads-up to consumers about APIs that are on the way out.
Unfortunately,
there’s currently no way to designate deprecation
in terms of the library version
(the list of platforms are hard-coded by the compiler).
While it’s a bit of a hack,
you could communicate deprecation
by specifying an obsolete / non-existent Swift language version
like so:
@available(swift, deprecated: 0.0.1, message: "Deprecated in 1.2.0")funcgoing(going:Gone...){}
If you’re among the l33t coders who have “hard mode” turned on
(“Treat Warnings as Errors” a.k.a. SWIFT_TREAT_WARNINGS_AS_ERRORS),
but find yourself stymied by deprecation warnings,
here’s a cheat code you can use:
classCustomView{@available(iOS, introduced: 10, deprecated: 13, message: "😪")funcmethod(){}}CustomView().method()// "'method()' was deprecated in iOS 13: 😪"protocolIgnoringMethodDeprecation{funcmethod()}extensionCustomView:IgnoringMethodDeprecation{}(CustomView()asIgnoringMethodDeprecation).method()// No warning! 😁
As an occupation,
programming epitomizes the post-scarcity ideal
of a post-industrial world economy
in the information age.
But even so far removed from physical limitations,
we remain inherently constrained by forces beyond our control.
However,
with careful and deliberate practice,
we can learn to make use of everything available to us.
It’s hard to get excited when new features come to Objective-C.
These days,
any such improvements are in service of Swift interoperability
rather than an investment in the language itself
(see nullability
and lightweight generics).
The genesis of this new language feature is unclear;
the most we have to go on is an Apple-internal Radar number
(2684889),
which doesn’t tell us much beyond its relative age
(sometime in the early ’00s, by our estimation).
Fortunately,
the feature landed
with enough documentation and test coverage
to get a good idea of how it works.(Kudos to implementor Pierre Habouzit,
review manager John McCall,
and the other LLVM contributors).
This week on NSHipster,
we’re taking this occasion to review Objective-C method dispatching
and try to understand the potential impact of this new language feature
on future codebases.
To understand the significance of direct methods,
you need to know a few things about the Objective-C runtime.
But let’s start our discussion one step before that,
to the origin of OOP itself:
Object-Oriented Programming
Alan Kay coined the term “object-oriented programming in the late 1960s.
With the help of Adele Goldberg, Dan Ingalls, and his other colleagues at
Xerox PARC,
Kay put this idea into practice in the ’70s
with the creation of the Smalltalk programming language.
In the 1980s,
Brad Cox and Tom Love started work the first version of Objective-C,
a language that sought to take the object-oriented paradigm of Smalltalk
and implement it on solid fundamentals of C.
Through a series of fortuitous events in the ’90s,
the language would come to be the official language of NeXT,
and later, Apple.
For those of us who started learning Objective-C in the iPhone era,
the language was often seen as yet another proprietary Apple technology —
one of a myriad, obscure byproducts of the company’s
“Not invented here”
(NIH) culture.
However,
Objective-C isn’t just “an object-oriented C”,
it’s one of the original object-oriented languages,
with as strong a claim to OOP credentials as any other.
Now, what does OOP mean?
That’s a good question.
’90s era hype cycles have rendered the term almost meaningless.
However,
for our purposes today,
let’s focus on something Alan Kay wrote in 1998:
I’m sorry that I long ago coined the
term “objects” for this topic because it gets many people to
focus on the lesser idea.
The big idea is “messaging” […] > Alan Kay
Dynamic Dispatch and the Objective-C Runtime
In Objective-C,
a program consists of a collection of objects
that interact with each other by passing messages that, in turn,
invoke methods, or functions.
This act of message passing is denoted by square bracket syntax:
[someObjectaMethod:withAnArgument];
When Objective-C code is compiled,
message sends are transformed into calls to a function calledobjc_msgSend
(literally “send a message to some object with an argument”).
The first argument is the receiver (self for instance methods)
The second argument is _cmd: the selector, or name of the method
Any method parameters are passed as additional function arguments
objc_msgSend is responsible for determining
which underlying implementation to call in response to this message,
a process known as method dispatch.
In Objective-C,
each class (Class) maintains a dispatch table to resolve messages sent at runtime.
Each entry in the dispatch table is a method (Method)
that keys a selector (SEL)
to a corresponding implementation (IMP),
which is a pointer to a C function.
When an object receives a message,
it consults the dispatch table of its class.
If it can find an implementation for the selector,
the associated function is called.
Otherwise,
the object consults the dispatch table of its superclass.
This continues up the inheritance chain until a match is found
or the root class (NSObject) deems the selector to be unrecognized.
If you think all of this indirection sounds like a lot of work…
in a way,
you’d be right!
If you have a hot path in your code,
an expensive method that’s called frequently,
you could imagine some benefit to avoiding all of this indirection.
To that end,
some developers have used C functions as a way around dynamic dispatch.
Direct Dispatch with a C Function
As we saw with objc_msgSend,
any method invocation can be represented by an equivalent function
by passing implicit self as the first argument.
For example,
consider the following declaration of an Objective-C class
with a conventional, dynamically-dispatched method.
If a developer wanted to implement some functionality on MyClass
without going through the whole message sending shebang,
they could declare a static C function
that took an instance of MyClass as an argument.
Here’s how each of these approaches translates to the call site:
MyClass*object=[[[MyClass]alloc]init];// Dynamic Dispatch[objectdynamicMethod];// Direct DispatchdirectFunction(object);
Direct Methods
A direct method has the look and feel of a conventional method,
but has the behavior of a C function.
When a direct method is called,
it directly calls its underlying implementation
rather than going through objc_msgSend.
With this new LLVM patch,
you now have a way to annotate Objective-C methods
to avoid participation in dynamic dispatch selectively.
objc_direct, @property(direct), and objc_direct_members
To make an instance or class method direct,
you can mark it with the objc_directClang attribute.
Likewise,
the methods for an Objective-C property can be made direct
by declaring it with the direct property attribute.
When an @interface for
a category or class extension is annotated with the objc_direct_members attribute,
all method and property declarations contained within it
are considered to be direct,
unless previously declared by that class.
Annotating an @implementation with objc_direct_members has a similar effect,
causing non-previously declared members to be deemed direct,
including any implicit methods resulting from property synthesis.
Applying these annotations to our example from before,
we can see how direct and dynamic methods are indistinguishable at the call site:
MyClass*object=[[[MyClass]alloc]init];// Dynamic Dispatch[objectdynamicMethod];// Direct Dispatch[objectdirectMethod];
Direct methods seem like a slam dunk feature
for the performance-minded developers among us.
But here’s the twist:
In most cases,
making a method direct probably won’t have a noticeable performance advantage.
As it turns out,objc_msgSend is surprisingly fast.
Thanks to aggressive caching, extensive low-level optimization,
and intrinsic performance characteristics of modern processors,objc_msgSend has an extremely low overhead.
We’re long past the days when iPhone hardware
could reasonably be described as a resource-constrained environment.
So unless Apple is preparing for a new embedded platform
(AR glasses, anyone?),
the most reasonable explanation we have for
Apple implementing Objective-C direct methods in 2019
stems from something other than performance.
Hidden Motives
When an Objective-C method is marked as direct,
its implementation has hidden visibility.
That is,
direct methods can only be called within the same module(or to be pedantic,linkage unit).
It won’t even show up in the Objective-C runtime.
Hidden visibility has two direct advantages:
Smaller binary size
No external invocation
Without external visibility
or a way to invoke them dynamically from the Objective-C runtime,
direct methods are effectively private methods.
While hidden visibility can be used by Apple
to prevent swizzling and private API use,
that doesn’t seem to be the primary motivation.
According to Pierre,
who implemented this feature,
the main benefit of this optimization is code size reduction.
Reportedly,
the weight of unused Objective-C metadata
can account for 5 – 10% of the __text section in the compiled binary.
You could imagine that,
from now until next year’s developer conference,
a few engineers could go through each of the SDK frameworks,
annotating private methods with objc_direct
and private classes with objc_direct_members
as a lightweight way to progressively tighten its SDK.
If that’s true,
then perhaps it’s just as well that we’ve become skeptical of new Objective-C features.
When they’re not in service of Swift,
they’re in service of Apple.
Despite its important place in the history of programming and Apple itself,
it’s hard not to see Objective-C as just that — history.
#pragma declarations are a mark of craftsmanship in Objective-C.
Although originally used to make source code compatible across different compilers,
Xcode-savvy programmers use #pragma declarations to very different ends.
Whereas other preprocessor directives
allow you to define behavior when code is executed,#pragma is unique in that it gives you controlat the time code is being written—
specifically,
by organizing code under named headings
and inhibiting warnings.
As we’ll see in this week’s article,
good developer habits start with #pragma mark.
Organizing Your Code
Code organization is a matter of hygiene.
How you structure your code is a reflection of you and your work.
A lack of convention and internal consistency indicates
either carelessness or incompetence —
and worse,
it makes a project more challenging to maintain over time.
We here at NSHipster believe that
code should be clean enough to eat off of.
That’s why we use #pragma mark to divide code into logical sections.
If your class overrides any inherited methods,
organize them under common headings according to their superclass.
This has the added benefit of
describing the responsibilities of each ancestor in the class hierarchy.
An NSInputStream subclass, for instance,
might have a group marked NSInputStream,
followed by NSStream,
and then finally NSObject.
If your class adopts any protocols,
it makes sense to group each of their respective methods
using a #pragma mark header with the name of that protocol(bonus points for following the same order as the original declarations).
Finding it difficult to make sense of your app’s MVC architecture?
(“Massive View Controller”, that is.)#pragma marks allow you to divide-and-conquer!
With a little practice,
you’ll be able to identify and organize around common concerns with ease.
Some headings that tend to come up regularly include
initializers,
helper methods,
@dynamic properties,
Interface Builder outlets or actions,
and handlers for notification or
Key-Value Observing (KVO) selectors.
Putting all of these techniques together,
the @implementation for
a ViewController class that inherits from UITableViewController
might organize Interface Builder actions together at the top,
followed by overridden view controller life-cycle methods,
and then methods for each adopted protocol,
each under their respective heading.
@implementationViewController-(instancetype)init{…}#pragma mark - IBAction-(IBAction)confirm:(id)sender{…}-(IBAction)cancel:(id)sender{…}#pragma mark - UIViewController-(void)viewDidLoad{…}-(void)viewDidAppear:(BOOL)animated{…}#pragma mark - UITableViewDataSource-(NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{…}#pragma mark - UITableViewDelegate-(void)tableView:(UITableView*)tableViewdidSelectRowAtIndexPath:(NSIndexPath*)indexPath{…}@end
Not only do these sections make for easier reading in the code itself,
but they also create visual cues to
the Xcode source navigator and minimap.
Inhibiting Warnings
Do you know what’s even more annoying than poorly-formatted code?
Code that generates warnings —
especially 3rd-party code.
Is there anything more irksome than a vendor SDK that generates dozens of warnings
each time you hit ⌘R?
Heck,
even a single warning is one too many for many developers.
Warnings are almost always warranted,
but sometimes they’re unavoidable.
In those rare circumstances where you are absolutely certain that
a particular warning from the compiler or static analyzer is errant,#pragma comes to the rescue.
Now,
deprecation is something to be celebrated.
It’s a way to responsibly communicate future intent to API consumers
in a way that provides enough time for them to
migrate to an alternative solution.
But you might not feel so appreciated if you have
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS enabled in your project.
Rather than being praised for your consideration,
you’d be admonished for implementing a deprecated class.
One way to silence the compiler would be to
disable the warnings by setting -Wno-deprecated-implementations.
However,
doing this for the entire project would be too coarse an adjustment,
and doing this for this file only would be too much work.
A better option would be to use #pragma
to inhibit the unhelpful warning
around the problematic code.
Using #pragma clang diagnostic push/pop,
you can tell the compiler to ignore certain warnings
for a particular section of code:
Before you start copy-pasting this across your project
in a rush to zero out your warnings,
remember that,
in the overwhelming majority of cases,
Clang is going to be right.
Fixing an analyzer warning is strongly preferred to ignoring it,
so use #pragma clang diagnostic ignored as a method of last resort.
Though it skirts the line between comment and code,#pragma remains a vital tool in the modern app developer’s toolbox.
Whether it’s for grouping methods or suppressing spurious warnings,
judicious use of #pragma can go a long way
to make projects easier to understand and maintain.
Like the thrift store 8-track player you turned into that lamp in the foyer,#pragma remains a curious vestige of the past:
Once the secret language of compilers,
now re-purposed to better-communicate intent to other programmers.How delightfully vintage!
Birdwatchers refer to it as (and I swear I’m not making this up)“Jizz”:
the general characteristics that form an overall impression of a thing.
Walking through the forests of the Pacific Northwest,
a birder would know a nighthawk from other little brown jobs
from its distinct vocalization,
or a grey-cheeked thrush by its white-dark-white underwing pattern.
Looking up in the sky,
there’d be no mistaking a Flying-V formation of migratory geese
from the undulating murmuration of starlings.
And while a twitcher would be forgiven for
mistaking a coot for a duck at the water’s edge,
their scaley, non-webbed feet are an obvious tell to an ornithophile.
The usefulness of jizz isn’t limited to amateur ornithology, either.
We can distinguish varieties of programming languages
based on their defining characteristics:
Go with its tell-tale couplets of if err,
Rust with its unpronounceable, consonant-laden keywords, pub, fn, and mut,
Perl with its special characters that read like Q*bert swearing.
Lisp’s profusion of parentheses is an old cliché at this point;
our favorite telling is
that one joke
about the stolen last page of a Lisp program’s printed source code.
If we were to go code-watching for the elusive Objective-C species,
what would we look for?
Square brackets,
ridiculously long method names,
and @’s.
@, or “at” sign compiler directives,
are as central to understanding Objective-C’s gestalt
as its ancestry and underlying mechanisms.
Those little cinnamon roll glyphs are the sugary glue
that allows Objective-C to be such a powerful, expressive language,
and yet still compile down to C.
So varied and disparate are its uses that
the only accurate way to describe what @ means on its own is “shorthand for something to do with Objective-C”.
They cover a broad range in usefulness and obscurity,
from staples like @interface and @implementation
to ones you could go your whole career without spotting,
like @defs and @compatibility_alias.
But to anyone aspiring to be an NSHipster,
knowledge of every @ directives
is tantamount to a birder’s familiarity with
the frenetic hovering of a hummingbird,
the commanding top knot of a Mountain quail, or
the eponymous “cuckoo” of Coccyzus americanus.
Interface & Implementation
@interface and @implementation are the first things you encounter
when you start learning Objective-C:
What you don’t learn about until later on are categories and class extensions.
Categories allow you to extend the behavior of existing classes
by adding new class or instance methods.
As a convention,
categories are defined in their own .{h,m} files:
Categories are particularly useful for convenience methods on standard framework classes (just don’t go overboard with your utility functions).
Extensions look like categories
but omit the category name.
They’re typically declared before an @implementation
to specify a private interface
or override properties declared in the public interface:
And that’s not all —
there’s also the class attribute,
which lets you declare a class property using
the same, familiar instance property syntax,
as well as the forthcoming direct attribute,
which will let you opt in to direct method dispatch.
Forward Class Declarations
Occasionally,@interface declarations will reference an external type in a property or as a parameter.
Rather than adding an #import statement in the interface,
you can use a forward class declaration in the header
and import the necessary in the implementation.
@class
Creates a forward declaration,
allowing a class to be referenced before its actual declaration.
Shorter compile times,
less chance of cyclical references;
you should get in the habit of doing this if you aren’t already.
Instance Variable Visibility
It’s a matter of general convention that
classes provide state and mutating interfaces through properties and methods,
rather than directly exposing ivars.
Nonetheless,
in cases where ivars are directly manipulated,
there are the following visibility directives:
@public
Instance variable can be read and written to directly
using the following notation:
object->_ivar=…
@package
Instance variable is public,
except outside of the framework in which it is specified
(64-bit architectures only)
@protected
Instance variable is only accessible to its class and derived classes
There’s a distinct point early in an Objective-C programmer’s evolution
when they realize that they can define their own protocols.
The beauty of protocols is that they let you design contracts
that can be adopted outside of a class hierarchy.
It’s the egalitarian mantra at the heart of the American Dream:
It doesn’t matter who you are or where you come from;
anyone can achieve anything if they work hard enough.
@protocol…@end
defines a set of methods to be implemented by any conforming class,
as if they were added to the interface of that class directly.
Architectural stability and expressiveness without the burden of coupling?
Protocols are awesome.
Requirement Options
You can further tailor a protocol by specifying methods as required or optional.
Optional methods are stubbed in the interface,
so as to be auto-completed in Xcode,
but do not generate a warning if the method isn’t implemented.
Protocol methods are required by default.
The syntax for @required and @optional follows that of the visibility macros:
Objective-C communicates unexpected state primarily through NSError.
Whereas other languages would use exception handling for this,
Objective-C relegates exceptions to truly exceptional behavior.
@ directives are used for the traditional convention of try/catch/finally blocks:
@try{// attempt to execute the following statements[selfgetValue:&valueerror:&error];// if an exception is raised, or explicitly thrown...if(error){@throwexception;}}@catch(NSException*e){…}@finally{// always executed after @try or @catch[selfcleanup];}
Literals
Literals are shorthand notation for specifying fixed values,
and their availability in a language
is directly correlated with programmer happiness.
By that measure,
Objective-C has long been a language of programmer misery.
Object Literals
For years,
Objective-C only had literals for NSString values.
But with the release of the Apple LLVM 4.0 compiler,
there are now literals for NSNumber, NSArray, and NSDictionary.
@""
An NSString object initialized with
the text inside the quotation marks.
@42 / @3.14 / @YES / @'Z'
An NSNumber object initialized with
the adjacent value using the pertinent class constructor,
such that @42→ [NSNumber numberWithInteger:42] and @YES→ [NSNumber numberWithBool:YES]. (You can use suffixes to further specify type,
like @42U→ [NSNumber numberWithUnsignedInt:42U])
@[]
An NSArray object initialized with
a comma-delimited list of objects as its contents.
It uses the +arrayWithObjects:count: class constructor method,
which is a more precise alternative to the more familiar +arrayWithObjects:.
@{}
An NSDictionary object initialized with key-value pairs as its contents
using the format: @{@"someKey" : @"theValue"}.
@()
A boxed expression using the appropriate object literal for the enclosed value (for example, NSString for const char*, NSNumber for int, and so on).
This is also the designated way to use number literals with enum values.
Objective-C Literals
Selectors and protocols can be passed as method parameters.@selector() and @protocol() serve as pseudo-literal directives
that return a pointer to a particular selector (SEL) or protocol (Protocol *).
@selector()
Provides an SEL pointer to a selector with the specified name.
Used in methods like -performSelector:withObject:.
@protocol()
Provides a Protocol * pointer to the protocol with the specified name.
Used in methods like -conformsToProtocol:.
C Literals
Literals can also work the other way around,
transforming Objective-C objects into C values.
These directives in particular allow us to peek underneath the Objective-C veil
to see what’s really going on.
Did you know that all Objective-C classes and objects are just glorified structs?
Or that the entire identity of an object hinges on a single isa field in that struct?
For most of us,
most of the time,
this is an academic exercise.
But for anyone venturing into low-level optimizations,
this is simply the jumping-off point.
@encode()
Provides the type encoding of a type.
This value can be used as the first argument in NSCoder -encodeValueOfObjCType:at:.
@defs()
Provides the layout of an Objective-C class.
For example, struct { @defs(NSObject) }
declares a struct with the same fields as an NSObject:
Optimizations
Some @ compiler directives provide shortcuts for common optimizations.
@autoreleasepool {…}
If your code contains a tight loop that creates lots of temporary objects,
you can use the @autoreleasepool directive to
aggressively deallocate these short-lived, locally-scoped objects.@autoreleasepool replaces and improves upon the old NSAutoreleasePool,
which was significantly slower and unavailable with ARC.
@synchronized(object) {…}
Guarantees the safe execution of a particular block within a specified context
(usually self).
Locking in this way is expensive, however,
so for classes aiming for a particular level of thread safety,
a dedicated NSLock property
or the use of low-level primitives like GCD
are preferred.
Compatibility
When Apple introduces a new API,
it’s typically available for the latest SDK only.
If you want to start using these APIs in your app
without dropping backward compatibility,
you can create a compatibility alias.
For example,
back when UICollectionView was first introduced in iOS 6,
many developers incorporated a 3rd-party library calledPSTCollectionView,
which uses @compatibility_alias to provide a backwards-compatible,
drop-in replacement for UICollectionView:
You can use the same approach today to strategically adopt new APIs in your app,
alongside the next and final @ compiler directive in this week’s article:
Availability
Achieving backwards or cross-platform compatibility in your app
can often feel like a high-wire act.
If you so much as glance towards an unavailable class or method,
it could mean curtains for your app.
That’s why the new features in Clang 5.0 came as such a relief.
Now developers have a compiler-provide safety net
to warn them whenever an unavailable API is referenced
for one of your supported targets.
@available
Use in an if statement to have the compiler
conditionally execute a code path based on the platform availability.
For example,
if you wanted to use a fancyNewMethod in the latest version of macOS,
but provide a fallback for older versions of macOS:
Much like the familiar call of a warbler
or the tell-tale plumage of a peacock,
the @ sigil plays a central role
in establishing Objective-C’s unique identity.
It’s a versatile, power-packed character
that embodies the underlying design and mechanisms of the language.
So be on the lookout for its many faces
as you wander through codebases, new or familiar.
Programming is about typing.
And programming languages are typically judged by how much they make you type —
in both senses of the word.
Swift is beloved for being able to save us a few keystrokes
without compromising safety or performance,
whether it’s through
implicit typing or
automatic synthesis of protocols like
Equatable andHashable.
But the OG
ergonomic feature of Swift is undoubtedly
automatic synthesis of RawRepresentable conformance
for enumerations with raw types.
You know…
the language feature that lets you do this:
enumGreeting:String{case hello = "hello"case goodbye // implicit raw value of "goodbye"}enumSortOrder:Int{case ascending = -1case same // implicit raw value of 0case descending // implicit raw value of 1}
Though “enum + RawValue” has been carved into the oak tree of our hearts
since first we laid eyes on that language with a fast bird,
few of us have had occasion to consider
what RawRepresentable means outside of autosynthesis.
This week,
we invite you to do a little extra typing
and explore some untypical use cases for the RawRepresentable protocol.
In Swift,
an enumeration can be declared withraw value syntax.
For any enumeration with a string, integer, or floating-point raw type,
the Swift compiler automatically adds RawRepresentable conformance.
When developers first start working with Swift,
they inevitably run into situations where raw value syntax doesn’t work:
Enumerations with raw values other than Int or String
Enumerations with associated values
Upon seeing those bright, red error sigils,
many of us fall back to a more conventional enumeration,
failing to realize that what we wanted to do wasn’t impossible,
but rather just slightly beyond what the compiler can do for us.
RawRepresentable with C Raw Value Types
The primary motivation for raw value enumerations is
to improve interoperability.
Quoting again from the docs:
Using the raw value of a conforming type
streamlines interoperation with Objective-C and legacy APIs.
This is true of Objective-C frameworks in the Apple SDK,
which declare enumerations with NS_ENUM.
But interoperability with other C libraries is often less seamless.
Consider the task of interfacing with libcmark,
a library for working with Markdown according to theCommonMark spec.
Among the imported data types is cmark_node_type,
which has the following C declaration:
typedefenum{/* Error status */CMARK_NODE_NONE,/* Block */CMARK_NODE_DOCUMENT,CMARK_NODE_BLOCK_QUOTE,…CMARK_NODE_HEADING,CMARK_NODE_THEMATIC_BREAK,CMARK_NODE_FIRST_BLOCK=CMARK_NODE_DOCUMENT,CMARK_NODE_LAST_BLOCK=CMARK_NODE_THEMATIC_BREAK,…}cmark_node_type;
We can immediately see a few details that would need to be ironed out
along the path of Swiftification —
notably,
1) the sentinel NONE value, which would instead be represented by nil, and
2) the aliases for the first and last block values,
which wouldn’t be encoded by distinct enumeration cases.
Attempting to declare a Swift enumeration
with a raw value type of cmark_node_type results in a compiler error.
enumNodeType:cmark_node_type{}// Error
However,
that doesn’t totally rule out cmark_node_type from being a RawValue type.
Here’s what we need to make that happen:
It’s a far cry from being able to say case document = CMARK_NODE_DOCUMENT,
but this approach offers a reasonable solution
that falls within the existing semantics of the Swift standard library.
That debunks the myth aboutInt and String being the only types that can be a raw value.
What about that one about associated values?
RawRepresentable and Associated Values
In Swift,
an enumeration case can have one or more associated values.
Associated values are a convenient way to introduce some flexibility
into the closed semantics of enumerations
and all the benefits they confer.
There are three numbers in computer science: 0, 1, and N.
enumNumber{case zerocase onecase n(Int)}
Because of the associated value on n,
the compiler can’t automatically synthesize an Int raw value type.
But that doesn’t mean we can’t roll up our sleeves and pick up the slack.
What’s really interesting is that our contrived little enumeration type
benefits from the same, small memory footprint
that you get from using enumerations in more typical capacities:
Without built-in support for type unions
or an analog to the open access modifier for classes,
there’s nothing that an API provider can do,
for example,
to prevent a consumer from doing the following:
structAether:Elemental{}
Any switch statement over a type-erased Elemental value
using is checks will necessarily have a default case.
Until we have a first-class language feature for providing such guarantees,
we can recruit enumerations and raw values for a reasonable approximation:
extensionElement:RawRepresentable{init?(rawValue:Elemental.Type){switchrawValue{case is Earth.Type:self=.earthcase is Water.Type:self=.watercase is Air.Type:self=.aircase is Fire.Type:self=.firedefault:returnnil}}varrawValue:Elemental.Type{switchself{case .earth: return Earth.selfcase .water: return Water.selfcase .air: return Air.selfcase .fire: return Fire.self}}}
Returning one last time to the docs,
we’re reminded that:
With a RawRepresentable type,
you can switch back and forth between
a custom type and an associated RawValue type
without losing the value of the original RawRepresentable type.
From the earliest days of the language, RawRepresentable has been relegated to
the thankless task of C interoperability.
But looking now with a fresh set of eyes,
we can now see it for in all its injective glory.
So the next time you find yourself with an enumeration
whose cases broker in discrete, defined counterparts,
consider adopting RawRepresentable to formalize the connection.
Visual Studio Code (VSCode)
is a cross-platform text and source code editor from Microsoft.
It’s one of the most exciting open source projects today,
with regular updates from hundreds of contributors.
VSCode was among the first tools to support
Language Server Protocol (LSP),
which has played a large part in providing a great developer experience,
in a variety of languages and technologies.
This week,
we’ll walk through the process of how to get started with
Swift’s new Language Server Protocol support in Visual Studio Code on macOS.
If you haven’t tried writing Swift outside Xcode,
or are already a VSCode user and new to the language entirely,
this article will tell you everything you need to know.
Step 0: Install Xcode
If you don’t already have Xcode installed on your machine,
open the Terminal app and run the following command:
$ xcode-select --install
Running this command presents a system prompt.
Click the “Get Xcode” button
and continue installation on the App Store.
Go to Swift.org
and download the latest trunk development snapshot
(at the time of writing, this was from November 16th, 2018).
Once it’s finished downloading,
run the package to install the Xcode toolchain.
To enable it,
open Xcode,
select the “Xcode > Preferences…” menu item (⌘,),
navigate to Components
and choose Swift Development Snapshot.
You can verify that everything is working as expected
by running the sourcekit-lsp command:
$ xcrun sourcekit-lsp
This command launches a new language server process,
but don’t worry if it doesn’t provide any feedback to STDOUT—
that means it’s working as intended.
Exit the process with an ETX signal (^C).
Step 3: Install Node and NPM
VSCode extensions are written in JavaScript / TypeScript.
If you’re not already set up for JS development,
you can download Node (a JavaScript run-time for outside the browser)
and npm (a package manager for Node)
with Homebrew using the following commands
or manually by following these instructions:
$ brew install node
To verify that you have a working installation,
run the following command:
$ npm --version6.13.4
Step 4: Build and Install SourceKit-LSP Extension for Visual Studio Code
From the command line,
clone the sourcekit-lsp repository
and navigate to Editors/vscode in the resulting directory.
Use npm to build the extension
and then use the code command to install it:
Now launch (or relaunch) VSCode and open a Swift project,
such as this one,
and test out Language Server Protocol support for Swift.
So there you have it —
the makings of a first-class Swift development experience outside of Xcode.
For now, Swift support for Language Server Protocol is limited,
but we couldn’t be more excited for the future of this project
and what it means for Swift beyond the Apple ecosystem.
At Apple we are making it a priority
to support high-quality tooling for all Swift developers,
including those working on non-Apple platforms.
We want to collaborate with the open-source community
and focus our efforts on building common infrastructure
that can be shared by Xcode and other editors and platforms.
To that end, [ … ] we’ve chosen to adopt LSP.
Argyrios Kyrtzidis, October 15th, 2018
This is arguably the most important decision Apple has made for Swift
since releasing the language as open source in 2014.
It’s a big deal for app developers,
and it’s an even bigger deal for Swift developers on other platforms.
To understand why,
this week’s article will take a look at
what problem the Language Server Protocol solves,
how it works,
and what its long-term impacts may be.
Imagine a grid
with each row representing a different programming language
(Swift, JavaScript, Ruby, Python, etc.)
and each column representing a different code editor
(Xcode, Visual Studio, Vim, Atom, etc.),
such that each cell represents
the level of support that a particular editor has for a language.
Up until recently,
what you’d find was a patchwork of compatibility
across the various combinations.
Some editors offered deep integration with a few languages
and little to no support for anything else,
whereas other editors aimed to be general-purpose
with at least a modicum of support for many languages.
(The term IDE
is often used to describe the former.)
Case in point:You’d be stubborn not to use Xcode for app development
and foolish to use it for anything else.
For an editor to have better support for a particular language,
it needs to write integration code —
either directly in the code base or via a plugin system.
Due to implementation differences across languages and editors,
improvements to, say,
Ruby support in Vim
wouldn’t translate into better support for Python,
nor could they be applied to make Ruby work better in Atom.
The end result: inconsistent support across technologies
and a lot of wasted effort.
The situation we described
is often referred to as an M × N problem,
where the number of integrations is the product ofM editors and N languages.
What the Language Server Protocol does is change this M × N problem
into a M + N problem.
Rather than an editor having to implement support for each language,
it only needs to support the LSP.
And in doing so, it gets the same level of functionality
for all languages that support the LSP.
Language Server Protocol provides a common set of functionality
for supported languages, including:
Syntax Highlighting
Automatic Formatting
Autocomplete
Syntax
Tooltips
Inline Diagnostics
Jump to Definition
Find References in Project
Advanced Text and Symbol Search
Rather than reinventing the wheel for each new technology,
tools and editors can invest in better usability
and more advanced functionality.
How Language Server Protocol Works
If you’re an iOS developer,
you may be most familiar with
the terms server and protocol
in the sense of communicating with web applications
in JSON format via HTTP.
This actually isn’t too far off from how the Language Server Protocol works.
In the case of LSP,
the client refers to the editor —
or more generally, the tool —
and the server refers to
an external program run locally in a separate process.
As for the protocol itself,
LSP resembles a simplified version of HTTP:
Each message consists of
a header part and a content part.
The header part has a required Content-Length field
containing the size of the content part in bytes,
and an optional Content-Type field
(application/vscode-jsonrpc; charset=utf-8 by default)
The content part uses JSON-RPC
to describe the structure of requests, responses, and notifications.
Whenever something happens in the tool,
such as the user jumping to the definition of a symbol,
the tool sends a request to the server.
The server receives that request
and then returns an appropriate response.
For example,
imagine that a user opens the following Swift code
in an Xcode-like editor that supported the Language Server Protocol:
classParent{}classChild:Parent{}
When the user ⌘-clicks the symbol Parent
in the inheritance clause on line 2,
the editor jumps to the definition of the Parent class on line 1.
Here’s how LSP enables this interaction behind the scenes:
First,
when the user opens the Swift code,
the editor launches its Swift language server in a separate process,
if it isn’t running already,
and performs any additional setup.
When the user executes the “jump to definition” command,
the editor sends the following request to its Swift language server:
Upon receiving this request,
the Swift language server uses a compiler tool likeSourceKit
to identify the corresponding code entity
and find the location of its declaration on the preceding line.
The language server then responds with the following message:
Finally,
the editor navigates to the file
(which, in this case, is already open),
moves the cursor to that range,
and highlights the token.
The beauty of this approach is that the editor did all of this
without knowing anything about the Swift programming language
other than that .swift files are associated with Swift code.
All the editor needs to do
is talk to the language server and update the UI.
And knowing how to do that,
the editor can follow the same procedure
to facilitate this interaction
for code written in any language with a language server implementation.
Language Server Protocol Support in Clang / LLVM
If the M + N diagram from before looks familiar,
it might be because it’s the same approach taken by LLVM.
At the core of LLVM is an intermediate representation (IR).
Supported languages generate IR using a compiler frontend,
and that IR can generate machine code
for any platform supported by a compiler backend.
The LLVM compiler frontend for C languages
is called Clang.
It’s also used by Swift for inter-operability with Objective-C.
In its recent 5.0.0 release,
Clang added a new tool called
Clangd,
LLVM’s implementation for the Language Server Protocol.
Now you might think, “So what?”
Apple is among the most prominent supporters of the LLVM project —
having, among other things,
employed the project’s founder, Chris Lattner, for over a decade.
Apple’s decision to switch from one obscure Clang tool to another
would seem to be an implementation detail (so to speak).
What makes this announcement quite interesting
is that Clangd appears to have been created entirely outside of Apple,
with significant contributions from Google and other companies.
This announcement signals a significant shift
in the direction of tooling development going forward —
something that would be confirmed 6 months later on the Swift.org forums.
Potential Consequences of Apple’s Support of Language Server Protocol
It’ll take some time to feel the full impact of these developments,
but believe me: your patience will be rewarded.
Here are just a few of what I believe will happen as a result of LSP
in the coming months and years.
Swift Becomes More Appealing as a General-Purpose Programming Language
Although Swift is used primarily for app development,
it was designed from the start to be
a capable general-purpose programming language.
BetweenSwift for TensorFlow,SwiftNIO,
and other projects,
we’re just starting to see the promise of
what Swift can do beyond the App Store.
Among the biggest factors holding Swift back from
mainstream adoption up to this point
has been its reliance on Xcode.
It’s a lot to ask, say, a web developer or machine learning engineer
to download Xcode just to try Swift
when there are so many great alternatives
with a much lower barrier to entry.
Support for the Language Server Protocol
should make it significantly easier for folks outside the Apple ecosystem
to evaluate Swift with the same, familiar tools they use for everything else.
Xcode Gets Better
Adopting LSP isn’t just about making Swift work better in other editors;
Xcode stands to benefit immensely, as well.
Consider this forum post
from Project Lead for Swift at Apple, Ted Kremenek:
The LSP service [Argyrios] is describing
will be functionally more powerful than SourceKit is today.
LSP is an opportunity for the Xcode team to take a fresh approach to
Swift integration,
and to capitalize on all of the improvements to the language and tooling
in the four years since its 1.0 release.
Our first glimpse into this overhauled infrastructure comes by way ofIndexStoreDB:
a powerful new API for querying code symbols in Swift projects
from a Clang index.
Getting Xcode to use our new LSP service
should make it viable to use other LSP services as well,
and it’s something that we are interested in,
but we don’t have specific plans to announce at this moment.
The main focus for the current efforts are to improve the story for Swift.
But once that’s done, it should be relatively straightforward
to have those benefits cascade down to other languages with LSP support.
The architecture of software
reflects the structure and values of the organizations that create it.
The converse is true as well, to some extent.
By adopting the open Language Server Protocol standard for Xcode,
Apple is making good on its commitment to the success of Swift
on platforms outside the Apple ecosystem.
And I think it’ll work:
tooling (or lack thereof) is often the key decider
in which technologies gain mindshare.
But perhaps more importantly,
I believe this decision demonstrates an increased willingness within
(at least some small part of) the company
for collaboration and transparency.
Last week,
Apple released the first beta of Xcode 11.4,
and it’s proving to be one of the most substantial updates in recent memory.
XCTest got a huge boost,
with numerous quality of life improvements,
and Simulator, likewise, got a solid dose ofTLC.
But it’s the changes to Swift that are getting the lion’s share of attention.
In Xcode 11.4,
Swift compile times are down across the board,
with many developers reporting improvements of 10 – 20% in their projects.
And thanks to a new diagnostics architecture,
error messages from the compiler are consistently more helpful.
This is also the first version of Xcode to ship with the newsourcekit-lsp server,
which serves to empower editors like VSCode
to work with Swift in a more meaningful way.
Yet,
despite all of these improvements
(which are truly an incredible achievement by Apple’s Developer Tools team),
much of the early feedback has focused on
the most visible additions to Swift 5.2.
And the response from the peanut galleries of
Twitter, Hacker News, and Reddit has been —
to put it charitably — “mixed”.
Granted,
both of those examples are terrible.
And that’s kinda the problem.
Too often,
coverage of “What’s New In Swift”
amounts to little more than a regurgitation of Swift Evolution proposals,
interspersed with poorly motivated (and often emoji-laden) examples.
Such treatments provide a poor characterization of Swift language features,
and — in the case of Swift 5.2 —
serves to feed into the popular critique that these are frivolous additions —
mere syntactic sugar.
This week,
we hope to reach the ooey gooey center of the issue
by providing some historical and theoretical context
for understanding these new features.
Syntactic Sugar in Swift
If you’re salty about “key path as function” being too sugary,
recall that the status quo
isn’t without a sweet tooth.
Consider our saccharine example from before:
"🧁🍭🍦".unicodeScalars.map{$0.properties.name}
That expression relies on at least four different syntactic concessions:
Trailing closure syntax,
which allows a final closure argument label of a function to be omitted
Anonymous closure arguments,
which allow arguments in closures to be used positionally ($0, $1, …)
without binding to a named variable.
Inferred parameter and return value types
Implicit return from single-expression closures
If you wanted to cut sugar out of your diet completely,
you’d best get Mavis Beacon on the line,
because you’ll be doing a lot more typing.
In fact,
as we’ll see in the examples to come,
Swift is a marshmallow world in the winter,
syntactically speaking.
From initializers and method calls to optionals and method chaining,
nearly everything about Swift could be described as a cotton candy melody —
it really just depends on where you draw the line between
“language feature” and “syntactic sugar”.
To understand why,
you have to understand how we got here in the first place,
which requires a bit of history, math, and computer science.
Get ready to eat your vegetables 🥦.
The λ-Calculus and Speculative Computer Science Fiction
All programming languages can be seen as various attempts to representthe λ-calculus.
Everything you need to write code —
variables, binding, application —
it’s all in there,
buried under a mass of Greek letters and mathematical notation.
Setting aside syntactic differences,
each programming language can be understood by
its combination of affordances for
making programs easier to write and easier to read.
Language features like
objects,
classes,
modules,
optionals,
literals,
and generics
are all just abstractions built on top of the λ-calculus.
Here you get a glimpse into three very different timelines;
ours is the reality in which ALGOL’s syntax (option #2)
“won out” over the alternatives.
From ALGOL 60,
you can draw a straight line from
CPL in 1963,
to BCPL in 1967
and C in 1972,
followed by Objective-C in 1984
and Swift in 2014.
That’s the lineage that informs what types are callable and how we call them.
Now, back to Swift…
Function Types in Swift
Functions are first-class objects in Swift,
meaning that they can be assigned to variables,
stored in properties,
and passed as arguments or returned as values from other functions.
What distinguishes function types from other values
is that they’re callable,
meaning that you can invoke them to produce new values.
Closures
Swift’s fundamental function type is the closure,
a self-contained unit of functionality.
letsquare:(Int)->Int={xinx*x}
As a function type,
you can call a closure by passing the requisite number of arguments
between opening and closing parentheses ()—
a la ALGOL.
square(4)// 16
Closures are so called because they close over and capture
references to any variables from the context in which they’re defined.
However, capturing semantics aren’t always desirable,
which is why Swift provides dedicated syntax to a special kind of closure
known as a function.
Functions
Functions defined at a top-level / global scope
are named closures that don’t capture any values.
In Swift,
you declare them with the func keyword:
funcsquare(_x:Int)->Int{x*x}square(4)// 16
Compared to closures,
functions have greater flexibility in how arguments are passed.
Function arguments can have named labels
instead of a closure’s unlabeled, positional arguments —
which goes a long way to clarify the effect of code at its call site:
Functions can also take variadic arguments,
implicit closures,
and default argument values
(allowing for magic expression literals like #file and #line):
And yet,
despite all of this flexibility for accepting arguments,
most functions you’ll encounter operate on an implicitself argument.
These functions are called methods.
Methods
A method is a function contained by a type.
Methods automatically provide access to self,
allowing them to effectively capture the instance on which they’re called
as an implicit argument.
Compared to more verbose languages like Objective-C,
the experience of writing Swift is, well, pretty sweet.
It’s hard to imagine any Swift developers objecting to what we have here
as being “sugar-coated”.
But like a 16oz can of Surge,
the sugar content of something is often surprising.
Turns out,
that example from before is far from innocent:
varqueue=Queue<Int>()// desugars to `Queue<Int>.init()`queue.push(1)// desugars to `Queue.push(&queue)(1)`
With this in mind,
let’s now take another look at callable types in Swift more generally.
{Type, Instance, Member} ⨯ {Static, Dynamic}
Since their introduction in Swift 4.2 and Swift 5, respectively,
many developers have had a hard time keeping@dynamicMemberLookup and @dynamicCallable
straight in their minds —
made even more difficult by the introduction of callAsFunction in Swift 5.2.
If you’re also confused,
we think the following table can help clear things up:
Static
Dynamic
Type
init
N/A
Instance
callAsFunction
@dynamicCallable
Member
func
@dynamicMemberLookup
Swift has always had static callable types and type members.
What’s changed in new versions of Swift
is that instances are now callable,
and both instances and members can now be called dynamically.
Let’s see what that means in practice,
starting with static callables.
This type can be called statically in the following ways:
letinstance=Static()// ❶ desugars to `Static.init()`Static.function()// ❷ (no syntactic sugar!)instance.function()// ❸ desugars to Static.function(instance)()instance()// ❹ desugars to `Static.callAsFunction(instance)()`
❶
Calling the Static type invokes an initializer
❷
Calling function on the Static type
invokes the corresponding static function member,
passing Static as an implicit self argument.
❸
Calling function on an instance of Static
invokes the corresponding function member,
passing the instance as an implicit self argument.
❹
Calling an instance of Static
invokes the callAsFunction() function member,
passing the instance as an implicit self argument.
This type can be called dynamically in a few different ways:
letinstance=Dynamic()// desugars to `Dynamic.init()`instance(1)// ❶ desugars to `Dynamic.dynamicallyCall(instance)(withArguments: [1])`instance(a:1)// ❷ desugars to `Dynamic.dynamicallyCall(instance)(withKeywordArguments: ["a": 1])`Dynamic.function(1)// ❸ desugars to `Dynamic[dynamicMember: "function"](1)`instance.function(1)// ❹ desugars to `instance[dynamicMember: "function"](1)`
❶
Calling an instance of Dynamic
invokes the dynamicallyCall(withArguments:) method,
passing an array of arguments
and Dynamic as an implicit self argument.
❷
Calling an instance of Dynamic
with at least one labeled argument
invokes the dynamicallyCall(withKeywordArguments:) method,
passing the arguments in a KeyValuePairs object
and Dynamic as an implicit self argument.
❸
Calling function on the Dynamic type
invokes the static dynamicMember subscript,
passing "function" as the key;
here, we call the returned anonymous closure.
❹
Calling function on an instance of Dynamic
invokes the dynamicMember subscript,
passing "function" as the key;
here, we call the returned anonymous closure.
Dynamism by Declaration Attributes
@dynamicCallable and @dynamicMemberLookup
are declaration attributes,
which means that they can’t be applied to existing declarations
through an extension.
So you can’t, for example,
spice upInt with Ruby-ish
natural language accessors:
@dynamicMemberLookup// ⚠︎ Error: '@dynamicMemberLookup' attribute cannot be applied to this declarationextensionInt{staticsubscript(dynamicMembermember:String)->Int?{letstring=member.replacingOccurrences(of:"_",with:"-")letformatter=NumberFormatter()formatter.numberStyle=.spellOutreturnformatter.number(from:string)?.intValue}}// ⚠︎ Error: Just to be super clear, this doesn't workInt.forty_two// 42 (hypothetically, if we could apply `@dynamicMemberLookup` in an extension)
Contrast this with callAsFunction,
which can be added to any type in an extension.
Sometimes to ship software,
you need to pair up and “ship” different technologies.
In building these features,
the “powers that be” have ordained that
Swift replace Python for Machine Learning.
Taking for granted that an incremental approach is best,
the way to make that happen is to allow
Swift to interoperate with Python
as seamlessly as it does with Objective-C.
And since Swift 4.2,
we’ve been getting pretty close.
The promise of additive changes is that they don’t change anything
if you don’t want them to.
You can continue to write Swift code
remaining totally ignorant of the features described in this article
(most of us have so far).
But let’s be clear:
there are no cost-free abstractions.
Economics uses the term negative externalities
to describe indirect costs incurred by a decision.
Although you don’t pay for these features unless you use them,
we all shoulder the burden of a more complex language
that’s more difficult to teach, learn, document, and reason about.
A lot of us who have been with Swift from the beginning
have grown weary of Swift Evolution.
And for those on the outside looking in,
it’s unfathomable that we’re wasting time on inconsequential “sugar” like this
instead of features that will really move the needle,
like async / await.
In isolation,
each of these proposals is thoughtful and useful — genuinely.
We’ve already had occasion to use a few of them.
But it can be really hard to judge things on their own technical merits
when they’re steeped in emotional baggage.
Everyone has their own sugar tolerance,
and it’s often informed by what they’re accustomed to.
Being cognizant of the drawbridge effect,
I honestly can’t tell if I’m out of touch,
or if it’s the children who are wrong…
Software development best practicesprescribe
strict separation of configuration from code.
Yet developers on Apple platforms
often struggle to square these guidelines with Xcode’s project-heavy workflow.
Understanding what each project setting does
and how they all interact with one another
is a skill that can take years to hone.
And the fact that much of this information
is buried deep within the GUIs of Xcode does us no favors.
Navigate to the “Build Settings” tab of the project editor,
and you’ll be greeted by hundreds of build settings
spread across layers of projects, targets, and configurations —
and that’s to say nothing of the other six tabs!
Fortunately,
there’s a better way to manage all of this configuration
that doesn’t involve clicking through a maze of tabs and disclosure arrows.
This week,
we’ll show you how you can use text-based xcconfig files
to externalize build settings from Xcode
to make your projects more compact, comprehensible, and powerful.
Xcode build configuration files,
more commonly known by their xcconfig file extension,
allow build settings for your app to be declared and managed without Xcode.
They’re plain text,
which means they’re much friendlier to source control systems
and can be modified with any editor.
Fundamentally,
each configuration file consists of a sequence of key-value assignments
with the following syntax:
BUILD_SETTING_NAME=value
For example,
to specify the Swift language version for a project,
you’d specify the SWIFT_VERSION build setting like so:
SWIFT_VERSION=5.0
At first glance,xcconfig files bear a striking resemblance to .env files,
with their simple, newline-delimited syntax.
But there’s more to Xcode build configuration files than meets the eye.
Behold!
Retaining Existing Values
To append rather than replace existing definitions,
use the $(inherited) variable like so:
BUILD_SETTING_NAME=$(inherited)additional value
You typically do this to build up lists of values,
such as the paths in which
the compiler searches for frameworks
to find included header files
(FRAMEWORK_SEARCH_PATHS):
FRAMEWORK_SEARCH_PATHS=$(inherited)$(PROJECT_DIR)
Xcode assigns inherited values in the following order
(from lowest to highest precedence):
Platform Defaults
Xcode Project xcconfig File
Xcode Project File Build Settings
Target xcconfig File
Target Build Settings
Referencing Values
You can substitute values from other settings
by their declaration name
with the following syntax:
BUILD_SETTING_NAME=$(ANOTHER_BUILD_SETTING_NAME)
Substitutions can be used to
define new variables according to existing values,
or inline to build up new values dynamically.
Setting Fallback Values for Referenced Build Settings
In Xcode 11.4 and later,
you can use the default evaluation operator
to specify a fallback value to use
if the referenced build setting evaluates as empty.
$(BUILD_SETTING_NAME:default=value)
Conditionalizing Build Settings
You can conditionalize build settings according to their
SDK (sdk), architecture (arch), and / or configuration (config)
according to the following syntax:
BUILD_SETTING_NAME[sdk=sdk] =value for specified sdkBUILD_SETTING_NAME[arch=architecture] =value for specified architectureBUILD_SETTING_NAME[config=configuration] =value for specified configuration
Given a choice between multiple definitions of the same build setting,
the compiler resolves according to specificity.
BUILD_SETTING_NAME[sdk=sdk][arch=architecture] =value for specified sdk and architecturesBUILD_SETTING_NAME[sdk=*][arch=architecture] =value for all other sdks with specified architecture
For example,
you might specify the following build setting
to speed up local builds by only compiling for the active architecture:
Including Build Settings from Other Configuration Files
A build configuration file can include settings from other configuration files
using the same #include syntax
as the equivalent C directive
on which this functionality is based:
#include "path/to/File.xcconfig"
As we’ll see later on in the article,
you can take advantage of this to build up cascading lists of build settings
in really powerful ways.
Creating Build Configuration Files
To create a build configuration file,
select the “File > New File…” menu item (⌘N),
scroll down to the section labeled “Other”,
and select the Configuration Settings File template.
Next, save it somewhere in your project directory,
making sure to add it to your desired targets
Once you’ve created an xcconfig file,
you can assign it to one or more build configurations
for its associated targets.
Now that we’ve covered the basics of using Xcode build configuration files
let’s look at a couple of examples of how you can use them
to manage development, stage, and production environments.
Customizing App Name and Icon for Internal Builds
Developing an iOS app usually involves
juggling various internal builds
on your simulators and test devices
(as well as the latest version from the App Store,
to use as a reference).
You can make things easier on yourself
with xcconfig files that assign each configuration
a distinct name and app icon.
If your backend developers comport themselves according to the aforementioned12 Factor App philosophy,
then they’ll have separate endpoints for
development, stage, and production environments.
On iOS,
perhaps the most common approach to managing these environments
is to use conditional compilation statements
with build settings like DEBUG.
However,
to pull these values programmatically,
we’ll need to take one additional step:
Accessing Build Settings from Swift
Build settings defined by
the Xcode project file, xcconfig files, and environment variables,
are only available at build time.
When you run the compiled app,
none of that surrounding context is available.(And thank goodness for that!)
But wait a sec —
don’t you remember seeing some of those build settings before
in one of those other tabs?
Info, was it?
As it so happens,
that info tab is actually just a fancy presentation of
the target’s Info.plist file.
At build time,
that Info.plist file is compiled
according to the build settings provided
and copied into the resulting app bundle.
Therefore,
by adding references to $(API_BASE_URL),
you can access the values for those settings
through the infoDictionary property of Foundation’s Bundle API.Neat!
Following this approach,
we might do something like the following:
Xcode projects are monolithic, fragile, and opaque.
They’re a source of friction for collaboration among team members
and generally a drag to work with.
Fortunately,xcconfig files go a long way to address these pain points.
Moving configuration out of Xcode and into xcconfig files
confers a multitude of benefits
and offers a way to distance your project from the particulars of Xcode
without leaving the Cupertino-approved “happy path”.
In 2002,
the United States Congress enacted
the Sarbanes–Oxley Act,
which introduced broad oversight to corporations
in response to accounting scandals at companies likeEnron and MCI WorldCom
around that time.
This act,PCI
and
HIPAA,
formed the regulatory backdrop
for a new generation ofIT companies
emerging from the dot-com bubble.
Around the same time,
we saw the emergence of ephemeral, distributed infrastructure —
what we now call “Cloud computing”—
a paradigm that made systems more capable but also more complex.
To solve both the regulatory and logistical challenges of the 21st century,
our field established best practices around application logging.
And many of the same tools and standards are still in use today.
This week on NSHipster,
we’re taking a look at
SwiftLog:
a community-driven, open-source standard for logging in Swift.
Developed by the Swift on Server community
and endorsed by theSSWG (Swift Server Work Group),
its benefit isn’t limited to use on the server.
Indeed,
any Swift code intended to be run from the command line
would benefit from adopting SwiftLog.
Read on to learn how.
As always,
an example would be helpful in guiding our discussion.
In the spirit of transparency and nostalgia,
let’s imagine writing a Swift program
that audits the finances of a ’00s Fortune 500 company.
importFoundationstructAuditor{funcwatch(_directory:URL)throws{…}funccleanup(){…}}do{letauditor=Auditor()defer{auditor.cleanup()}tryauditor.watch(directory:URL(string:"ftp://…/reports")!,extensions:["xls","ods","qdf"])// poll for changes}catch{print("error: \(error)")}
An Auditor type polls for changes to a directory(an FTP server, because remember: it’s 2003).
Each time a file is added, removed, or changed,
its contents are audited for discrepancies.
If any financial oddities are encountered,
they’re logged using the print function.
The same goes for issues connecting to the FTP,
or any other problems the program might encounter —
everything’s logged using print.
Simple enough.
We can run it from the command line like so:
$swift run auditstarting up...
ERROR: unable to reconnect to FTP#(try again after restarting PC under our desk)$swift run audit+ connected to FTP server
! accounting discrepancy in balance sheet
** Quicken database corruption! **
^C
shutting down...
Such a program might be technically compliant,
but it leaves a lot of room for improvement:
For one,
our output doesn’t have any timestamps associated with it.
There’s no way to know whether a problem was detected an hour ago or last week.
Another problem is that our output lacks any coherent structure.
At a glance,
there’s no straightforward way to isolate program noise from real issues.
Finally, —
and this is mostly due to an under-specified example—
it’s unclear how this output is handled.
Where is this output going?
How is it collected, aggregated, and analyzed?
The good news is that
all of these problems (and many others) can be solved
by adopting a formal logging infrastructure in your project.
Adopting SwiftLog in Your Swift Program
Adding SwiftLog to an existing Swift package is straightforward.
You can incorporate it incrementally
without making any fundamental changes to your code
and have it working in a matter of minutes.
Add swift-log as a Package Dependency
In your Package.swift manifest,
add swift-log as a package dependency and
add the Logging module to your target’s list of dependencies.
In POSIX systems,
programs operate on three, predefinedstreams:
File Handle
Description
Name
0
stdin
Standard Input
1
stdout
Standard Output
2
stderr
Standard Error
By default,Logger uses the built-in StreamLogHandler type
to write logged messages to standard output (stdout).
We can override this behavior to instead write to standard error (stderr)
by using the more complex initializer,
which takes a factory parameter:
a closure that takes a single String parameter (the label)
and returns an object conforming to LogHandler.
Replacing Print Statements with Logging Statements
Declaring our logger as a top-level constant
allows us to call it anywhere within our module.
Let’s revisit our example and spruce it up with our new logger:
do{letauditor=Auditor()defer{logger.trace("Shutting down")auditor.cleanup()}logger.trace("Starting up")tryauditor.watch(directory:URL(string:"ftp://…/reports")!,extensions:["xls","ods","qdf"])// poll for changes}catch{logger.critical("\(error)")}
The trace, debug, and critical methods
log a message at their respective log level.SwiftLog defines seven levels,
ranked in ascending order of severity from trace to critical:
Level
Description
.trace
Appropriate for messages that contain information only when debugging a program.
.debug
Appropriate for messages that contain information normally of use only when debugging a program.
.info
Appropriate for informational messages.
.notice
Appropriate for conditions that are not error conditions, but that may require special handling.
.warning
Appropriate for messages that are not error conditions, but more severe than .notice
.error
Appropriate for error conditions.
.critical
Appropriate for critical error conditions that usually require immediate attention.
If we re-run our audit example with our new logging framework in place,
we can see the immediate benefit of clearly-labeled, distinct severity levels
in log lines:
$swift run audit2020-03-26T09:40:10-0700 critical: Couldn't connect to ftp://…#(try again after plugging in loose ethernet cord)$swift run audit2020-03-26T10:21:22-0700 warning: Discrepancy in balance sheet
2020-03-26T10:21:22-0700 error: Quicken database corruption
^C
Beyond merely labeling messages,which — don’t get us wrong — is sufficient benefit on its own,
log levels provide a configurable level of disclosure.
Notice that the messages logged with the trace method
don’t appear in the example output.
That’s because Logger defaults to showing only messages
logged as info level or higher.
You can configure that by setting the Logger’s logLevel property.
A twelve-factor app never concerns itself with
routing or storage of its output stream.
It should not attempt to write to or manage logfiles.
Instead, each running process writes its event stream, unbuffered, to stdout.
Collecting, routing, indexing, and analyzing logs across a distributed system
often requires a constellation of open-source libraries and commercial products.
Fortunately,
most of these components traffic in a shared currency ofsyslog messages —
and thanks to
this package by Ian Partridge,
Swift can, as well.
That said,
few engineers have managed to retrieve this information
from the likes of Splunk
and lived to tell the tale.
For us mere mortals,
we might preferthis package by Will Lisac,
which sends log messages toSlack.
The good news is that we can use both at once,
without changing how messages are logged at the call site
by using another piece of the Logging module:MultiplexLogHandler.
With all of this in place,
our system will log everything in syslog format to standard out (stdout),
where it can be collected and analyzed by some other system.
But the real strength of this approach to logging
is that it can be extended to meet the specific needs of any environment.
Instead of writing syslog to stdout or Slack messages,
your system could send emails,
open SalesForce tickets,
or trigger a webhook to activate some IoT device.
Here’s how you can extend SwiftLog to fit your needs
by writing a custom log handler:
Creating a Custom Log Handler
The LogHandler protocol specifies the requirements for types
that can be registered as message handlers by Logger:
In the process of writing this article,
I created custom handler
that formats log messages for GitHub Actions
so that they’re surfaced on GitHub’s UI like so:
If you’re interested in making your own logging handler,
you can learn a lot by just browsing
the code for this project.
But I did want to call out a few points of interest here:
Conditional Boostrapping
When bootstrapping your logging system,
you can define some logic for how things are configured.
For logging formatters specific to a particular CI vendor,
for example,
you might check the environment to see if you’re running locally or on CI
and adjust accordingly.
importLoggingimportLoggingGitHubActionsimportstructFoundation.ProcessInfoLoggingSystem.bootstrap{labelin// Are we running in a GitHub Actions workflow?ifProcessInfo.processInfo.environment["GITHUB_ACTIONS"]=="true"{returnGitHubActionsLogHandler.standardOutput(label:label)}else{returnStreamLogHandler.standardOutput(label:label)}}
Testing Custom Log Handlers
Testing turned out to be more of a challenge than originally anticipated.
I could be missing something obvious,
but there doesn’t seem to be a way to create assertions about
text written to standard output.
So here’s what I did instead:
First,
create an internal initializer that takes a TextOutputStream parameter,
and store it in a private property.
Then,
in the test target,
create a type that adopts TextOutputStream
and collects logged messages to a stored property
for later inspection.
By using a @testable import
of the module declaring GitHubActionsLogHandler,
we can access that internal initializer from before,
and pass an instance of MockTextOutputStream to intercept logged messages.
Early intervention is among the most effective strategies for treating illness.
This is true not only for the human body, for society as a whole.
That’s why public health officials use contact tracing
as their first line of defense against
the spread of infectious disease in a population.
We’re hearing a lot about contact tracing these days,
but the technique has been used for decades.
What’s changed is that
thanks to the ubiquity of personal electronic devices,
we can automate what was — up until now — a labor-intensive, manual process.
Much like how “computer” used to be a job title held by humans,
the role of “contact tracer” may soon be filled primarily by apps.
On Friday,
Apple and Google announced a joint initiative
to deploy contact tracing functionality
to the billions of devices running iOS or Android
in the coming months.
As part of this announcement,
the companies shared draft specifications for thecryptography,hardware,
andsoftware
involved in their proposed solution.
In this article,
we’ll take a first look at these specifications —
particularly Apple’s proposed ContactTracing framework —
and use what we’ve learned to anticipate what
this will all look like in practice.
What is contact tracing?
Contact tracing is a technique used by public health officials
to identify people who are exposed to an infectious disease
in order to slow the spread of that illness within a population.
When a patient is admitted to a hospital
and diagnosed with a new, communicable disease,
they’re interviewed by health workers
to learn who they’ve interacted recently.
Any contacts whose interactions with the patient are then evaluated,
and if they’re diagnosed with the disease,
the process repeats with their known, recent contacts.
Contact tracing disrupts the chain of transmission.
It gives people the opportunity to isolate themselves before infecting others
and to seek treatment before they present symptoms.
It also allows decision-makers to make more informed
recommendations and policy decisions about additional measures to take.
If you start early and act quickly,
contact tracing gives you a fighting chance of containing an outbreak
before it gets out of hand.
Unfortunately, we weren’t so lucky this time around.
With over a million confirmed cases of COVID-19 worldwide,
many regions are well past the point where contact tracing is practical.
But that’s not to say that it can’t play an essential role
in the coming weeks and months.
“Only Apple (and Google) can do this.”
Since the outbreak,
various governments
and academics
have proposed standards for contact tracing.
But the most significant development so far came yesterday
with Apple and Google’s announcement of a joint initiative.
According to theNHS,
around 60% of adults in a population
would need to participate in order for digital contact tracing to be effective.
Researchers from the aforementioned institutions have noted
that the limits imposed by iOS on 3rd-party apps
make this level of participation unlikely.
On the one hand,
it feels weird to congratulate Apple for stepping in
to solve a problem it created in the first place.
But we can all agree that Friday’s announcement
is something to celebrate.
It’s no exaggeration to say that
this wouldn’t be possible without their help.
What are Apple and Google proposing as a solution?
At a high level,
Apple and Google are proposing a common standard
for how personal electronic devices (phones, tablets, watches)
can automate the process of contact tracing.
Instead of health workers chasing down contacts on the phone —
a process that can take hours, or even days —
the proposed system could identify every recent contact
and notify all of them within moments of a confirmed, positive diagnosis.
Let’s take them in turn,
starting with
cryptography (key derivation & rotation),
followed byhardware (Bluetooth),
andsoftware (app)
components.
Cryptography
When you install an app and open it for the first time,
the ContactTracing framework displays
a dialog requesting permission
to enable contact tracing on the device.
If the user accepts,
the framework generates a 32-byte cryptographic random number
to serve as the device’s Tracing Key.
The Tracing Key is kept secret, never leaving the device.
Every 24 hours,
the device takes the Tracing Key and the day number (0, 1, 2, …)
and uses
HKDF
to derive a 16-byte Daily Tracing Key.
These keys stay on the device,
unless you consent to share them.
Every 15 minutes,
the device takes the Daily Tracing Key and
the number of 10-minute intervals since the beginning of the day (0 – 143),
and uses
HMAC
to generate a new 16-byte Rolling Proximity Identifier.
This identifier is broadcast from the device usingBluetooth LE.
If someone using a contact tracing app gets a positive diagnosis,
the central health authority requests their Daily Tracing Keys
for the period of time that they were contagious.
If the patient consents,
those keys are then added to the health authority’s database as
Positive Diagnosis Keys.
Those keys are shared with other devices
to determine if they’ve had any contact over that time period.
Hardware
Bluetooth organizes communications between devices
around the concept of services.
A service describes a set of characteristics for accomplishing a particular task.
A device may communicate with multiple services
in the course of its operation.
Many service definitions are standardized
so that devices that do the same kinds of things communicate in the same way.
For example,
a wireless heart rate monitor
that uses Bluetooth to communicate to your phone
would have a profile containing two services:
a primary Heart Rate service and
a secondary Battery service.
Apple and Google’s Contact Tracing standard
defines a new Contact Detection service.
When a contract tracing app is running (either in the foreground or background),
it acts as a peripheral,
advertising its support for the Contact Detection service
to any other device within range.
The Rolling Proximity Identifier generated every 15 minutes
is sent in the advertising packet along with the 16-bit service UUID.
importCoreBluetooth// Contact Detection service UUIDletserviceUUID=CBUUID(string:"FD6F")// Rolling Proximity Identifierletidentifier:Data=…// 16 bytesletperipheralManager=CBPeripheralManager()letadvertisementData:[String:Any]=[CBAdvertisementDataServiceUUIDsKey:[serviceUUID]CBAdvertisementDataServiceDataKey:identifier]peripheralManager.startAdvertising(advertisementData)
At the same time that the device broadcasts as a peripheral,
it’s also scanning for other devices’ Rolling Proximity Identifiers.
Again, here’s how you might do that on iOS using Core Bluetooth:
Bluetooth is an almost ideal technology for contact tracing.
It’s on every consumer smart phone.
It operates with low power requirement,
which lets it run continuously without draining your battery.
And it just so happens to have a transmission range
that approximates the physical proximity required
for the airborne transmission of infectious disease.
This last quality is what allows contact tracing to be done
without resorting to location data.
Software
Your device stores any Rolling Proximity Identifiers it discovers,
and periodically checks them against
a list of Positive Diagnosis Keys sent from the central health authority.
Each Positive Diagnosis Key corresponds to someone else’s Daily Tracing Key.
we can derive all of the possible Rolling Proximity Identifiers
that it could advertise over the course of that day
(using the same HMAC algorithm
that we used to derive our own Rolling Proximity Identifiers).
If any matches were found among
your device’s list of Rolling Proximity Identifiers,
it means that you may have been in contact with an infected individual.
Suffice to say that digital contact tracing is really hard to get right.
Given the importance of getting it right,
both in terms of yielding accurate results and preserving privacy,
Apple and Google are providing SDKs for app developers to use
for iOS and Android, respectively.
All of the details we discussed about cryptography and Bluetooth
are managed by the framework.
The only thing we need to do as developers
is communicate with the user —
specifically, requesting their permission to start contact tracing
and notifying them about a positive diagnosis.
“Nobody ever got fired for buying IBMusing Objective-C.”
In our time of crisis,
what technology did Apple entrust with the fate of humanity?None other than Objective-C.
Although we only have the interface right now,
you can get a reasonable understanding of how everything works
from the APIs and documentation.
The biggest challenge you’ll face using the ContactTracing framework API
is dealing with all of its completion handlers.
Most of the functionality is provided through asynchronous APIs;
without a way to compose these operations,
you can easily find yourself nested 4 or 5 closures deep,
indented to the far side of your editor.
After some trial and error,
I managed to come up with a reasonable solution
following the delegate pattern.
The end result
should be familiar to anyone who’s ever used
CLLocationManager:
letmanager=ContactTracingManager.sharedmanager.delegate=DelegateClass()manager.startContactTracing()classDelegateClass:NSObject,ContactTracingManagerDelegate{funccontactTacingManager(_manager:ContactTracingManager,didReceiveExposureDetectionSummarysummary:CTExposureDetectionSummary){ifsummary.matchedKeyCount>1{// ⚠️ Possible exposure!}}}
For something that released under such an unyielding deadline,
mistakes are inevitable.
All in all,
I think the teams responsible for the ContactTracing framework
did an admirable job, and I extend my most sincere respect and gratitude
for their work.
Tracing a path back to normal life
Many of us have been sheltering in place for weeks, if not months.
Until a vaccine is developed and made widely available,
this is the most effective strategy we have for stopping the spread of the disease.
But experts are saying that a vaccine
could be anywhere from 9 to 18 months away.“What will we do until then?”
At least here in the United States,
we don’t yet have a national plan for getting back to normal,
so it’s hard to say.
What we do know is that
it’s not going to be easy,
and it’s not going to come all at once.
Once the rate of new infections stabilizes,
our focus will become containing new outbreaks in communities.
And to that end,
technology-backed contact tracing can play a crucial role.
From a technical perspective,
Apple and Google’s proposal gives us every reason to believe that
we can do contact tracing without compromising privacy.
However,
the amount of faith you put into this solution
depends on how much you trust
these companies and our governments in the first place.
Personally,
I remain cautiously optimistic.
Apple’s commitment to privacy has long been one of its greatest assets,
and it’s now more important than ever.
April is the month when apple trees start to bloom
up here in the Pacific Northwest.
All across Oregon’s Willamette Valley,
from Portland stretching south to Eugene,
long-barren branches sprout white, 5-petaled blossoms tinged with pink.
Any other year,
our family would be taking weekend trips
southwest to Sherwood or east towards Hood River
to visit their orchards.
Like the Fuji and Gala varieties that predominate in this region,
most apple cultivars are self-unfruitful—
which is to say that they require cross-pollination
to produce a good crop consistently.
When fertilized by the pollen of Fuji apple blossoms
(or those of Braeburn, Honey Crisp, or McIntosh varieties),
a Gala apple tree can yield 20 kilograms of fruit each season.
Those Gala trees, in return, endow their pollen on the Fuji apple trees
so that they too may blossom and bear one or two bushels of fruit, each.
The Dance of the Honey Bee
Appletree pollen is sticky.
In contrast with the windborne pollen of Alder, Birch, and Ash trees
(whose allergenic quality gave the Willamette its name,
meaning “valley of sickness” in the indigenous Kalapuya dialect),
appletrees rely on insects to transfer pollen —
particularly the honey bee.
Honey bees eat the pollen of flowers and convert their nectar into honey.
Some of the pollen sticks to their furry bodies,
which is inadvertently spread as they move from plant to plant.
When a scout bee encounters a food source,
she flies back to the hive
and communicates the location of that food source to male worker bees
by performing what’s called a waggle dance.
Performed in darkness on the vertical honeycomb surface in the hive,
she’s able to convey the precise location of new food sources to them
by flying a coffee bean-shaped pattern oriented in the direction of the sun.
It’s an incredible feat,
made all the more remarkable by the fact that bees are not, individually,
very intelligent.
Bees have brains on the order of 1 million neurons,
compared to the 100 billion neurons of a human brain.
If you move a food source closer and farther away from a hive,
you can see how the dance changes to convey this new information.
But move it just past some critical point,
and the dance becomes something entirely different:
instead of the waggle dance,
the bee performs a round dance
with a totally different cadence and flight path.
For many years,
the dance language of the bumblebee eluded all those who studied it.
That is until
a mathematician named Barbara Shipman
made the connection between a bee’s dance language
and the six-dimensional geometry of flag manifolds,
of all things.
What was the unique insight that allowed her to see what others couldn’t?
She grew up in a family of beekeepers
and cultivated an interest in mathematics and biology
that carried throughout her studies.
The leap from furry, buzzing insects to abstract geometry is inconceivable
unless you’re accustomed to looking at the world in that particular way.
The Rose that Grows From the Dunghill
When Apple first announced the Swift programming language in 2014,
it generated a flurry of excitement as we all tried to understand it.
One of the most powerful tools at our disposal for understanding is analogy:
New Thing is like Familiar Thing crossed with Another Thing.
So in those early days,
there was a lot of discussion within the community
attempting to compare and contrast Swift withHaskell or Go or Python or Scheme or Dylan.
Last year,
we saw something similar with at WWDC 2019.
Anyone familiar with React or Elm
immediately recognized their influence onSwiftUI and Combine
(even if Apple hadn’t come out and acknowledged it explicitly).
For some,
the connection between React and Elm with JavaScript
is an inconvenient truth.
I’ve seen numerous developers profess their disdain for the language
in ways that echo the old rivalry between iOS and Android
(or the even older rivalry between Mac and PC).
And yet,
there are countless examples of good ideas from “them”
being criticized and mocked until they’re incorporated into an Apple product:
Why did we consider these good ideas heretical until Apple did it?
Us vs. Them
Another flavor of this arises from the dichotomy between “Native” and “Hybrid”.
Whenever a company writes some blog post about React Native,
what inevitably follows is chorus of developers who either
praise the decision as courageous (if switching away)
or call it idiotic (if adopting it).
As developers,
we tend to align ourselves with enlightenment ideals like objectivity.
We say that we make decisions based in the indisputable reality of fact.
We consider ourselves reasonable and our decisions well-reasoned.
But to what extent is this actually true?
Do our thoughts lead us to our preferences,
or do we use thoughts to rationalize them after the fact?
In the 1960s and 70s,
the social psychologist Henri Tajfel and his colleagues
ran a series of experiments
that demonstrated how little it takes
for people to engage in intergroup discrimination.
In one experiment,
a group of boys were shown pictures with clusters of dots
and instructed to guess how many there were
as a test of their visual judgment.
The researchers split the group between
those who overestimated or underestimated the number.
Except, they only pretended to do this —
the boys were, in fact, randomly assigned to one of the two groups.
They were then given the task of allocating a fixed amount of real money
to other boys in the study.
The results surprised even the researchers:
Overwhelmingly, the boys chose outcomes where their assigned group
(under- or over-estimators) received more money than their counterparts —
even when that meant getting less overall.
Successful replication of these results in follow-up studies since then
presents compelling evidence of this peculiarity in human nature.
That a willingness to engage in “us vs. them” discrimination
can arise from completely artificial distinctions,
irrespective of any rationale of self-interest.
How else could you explain the intense tribalism
around how we talk to computers?
The Dream of Purity
When a developer proudly declares something to be“Pure Swift” or “100% JavaScript free”,
what are they really saying?
What’s presented as an objective statement of fact
often feels more like an oath of allegiance.
If you see the existence of competing technologies
as a fight between good and evil,
perhaps there are more important battles to fight.
If you can’t evaluate solutions as a series of trade-offs,
what chance do you have at accomplishing anything at all?
Yes,
there are real differences between technologies
and reasonable people disagree about
which one is best-suited to solve a particular problem.
But don’t mistake this for a moral contest.
Purity is an ideal;
a vision of the condition which needs yet to be created,
or such as needs to be diligently protected against the genuine or imagined odds.
Without such a vision, neither the concept of purity makes sense,
nor the distinction between purity and impurity can be sensibly drawn.
– Zygmunt Bauman
It’s of no practical consequence that
the grounds on which Apple Park sits today
were fruit orchards a hundred years ago.
But it’s poetic.
Long before it was “Silicon Valley”,
the stretch of land between the San Andreas and Hayward faults
was called “the Valley of Heart’s Delight”
for all of its fruit trees and flowering plants.
Dwelling on this,
you might reflect on how humans are like apple trees.
That we need a variety of different influences to reach our potential.
(Even self-starters benefit from a unique perspective).
You might then consider what we share in common with
the bees that pollinate apple trees.
Like them,
our success comes not from our individual intelligence,
but in our ability to share information.
Whether we’re like bees or like apples,
we come away learning the same lesson:
We can achieve remarkable results by working together.
Like everything else in 2020,
this year’s WWDC had to be a little different
if it was going to happen at all.
When Apple first announced that the conference would be fully remote,
nobody knew what that would look like, exactly.
What parts of the dubdub experience would be kept in this new format?
What details would be lost in translation?
Could they actually pull it off?
As it turns out,
going fully remote wasn’t merely good enough —
it was, in many ways, superior to the original thing.
There’s a lot to like about the new format.
The videos are well-produced,
and let each presenter’s personality really shine.
Everybody looks and sounds great.
Sessions are tight and well-paced.
Rather than stretching or cramming content into a fixed time slot,
they’re as long as they need to be.
And thanks to this more digestible format,
we’re starting to see WWDC clips being shared around,
which is new and refreshing.
To be honest,
it’s hard to imagine ever going back to a physical conference.
However,
as someone who had the privilege of attending WWDC in years past,
there are things I’m going to miss
(and some that I decidedly won’t).
🥰
😫
Refrigerators stocked with Odwalla smoothies
Trying to download the latest Xcode beta over hotel internet
Lunchtime sessions
Eating lunch at or around Moscone
WWDC track jackets saving the lives of first-time attendees from the cold of San Francisco summer
Being in San Francisco, generally
Eating burritos on the terrace of McEnery Convention Center during WWDC check-in
Being in San Jose, generally
Guessing who would be playing at Bash this year
Hearing the same handful of songs on repeat before and after every session
(this song in particular)
Watching Apple executives dance at Bash
Talking to people as you wait in line for the keynote on Monday morning
Waking up late and having to settle for watching from the overflow room
Leaving at the end of the week with a mix of hope, fear, and inspiration *
Being sick for the next week with dubdub flu
* I’d like to hold on this last point for a moment.
In the Before Times,
many of us traveled far from home to attend WWDC.
There was a physical and temporal delineation
between life before, during, and after the conference.
Flying out of SFO, SJC, or OAK,
you escaped Apple’s “reality distortion field”
and its surrounding echo chamber.
You returned to your normal life.
This year? Not so much.
WWDC 2020 was just another week in this bizarre existence amidst this pandemic.
Not only is there no escape from the “reality distortion field”,
there isn’t even a “normal life” for us to leave or return to.
So here we are,
filled with anxious excitement and fear;
our corporeal forms replaced by
Memoji floating in a black, digital void
lit only by the screens of our soon-to-be-obsolete MacBooks Pro.
Excitement
I don’t have a real sense of how everything went over this year.
There wasn’t any applause (or heckling) at this year’s keynote
to gauge the temperature in the room.
There were no parties to overhear shop talk and hot takes.
There was no line for lunch
to make small talk with a fellow attendee.
But if Twitter is anything to go on,
my overall impression is that everyone is really excited.
Which is fine. I get it.
But it should come as no surprise that things announced at WWDC are exciting —
that’s the whole point of having a developer conference in the first place.
Apple has the best marketing in the world,
and WWDC is Apple’s way of marketing to us.
Here’s the thing about excitement:
It’s kryptonite to developers.
Excitement messes with our ability to focus on one thing,
which is already a big struggle for a lot of us (myself included).
When you’re excited,
it’s almost impossible to get anything done.
There are plenty of voices in the community who are echoing this excitement.
I can’t add anything to that discussion.
And besides,
that’s not really where my head’s at right now.
Trivial Pursuit
I briefly considered reviving the NSHipster Quiz
for WWDC 2020,
but it didn’t feel right.
With everything going on in the world,
Apple trivia just isn’t where my head or heart are right now.
To give you a sense of what I mean, here’s what I had for Round 3,
whose theme was inspired by Richard Hamming:
Question 1.
What are the important problems of your field?
Question 2.
What important problems are you working on?
Question 3.
If what you are doing is not important,
why are working on it?
Temperance
If you’re serious about solving a problem,
you owe it to yourself to temper any excitement
that distracts you from making real progress.
In last year’s write-up for WWDC 2019,
I concluded with the following,
as a counterpoint to the conference’s theme of #mindblown🤯:
Taking care of yourself —
sleeping enough, eating right, exercising regularly —
will do more to improve your productivity
than any language or framework out there.
Your ability to communicate and collaborate with others
will always be a better predictor of success
than your choice of technology stack.
Your relationships with others
are the most significant factors of success and happiness in life.
I stand by this advice, boring as it may be.
It’s been an exciting week,
so take a moment to collect yourself.
Go on a walk. Take a hike. (Be safe about it.)
Do whatever you need to break free of the “reality distortion field”.
Once you do, you’ll have the necessary distance to determine
what new technologies you should pay attention to
and what you can ignore for now.
Chris Lattner often describes LLVM as a process of lowering.
You start at the highest level of abstraction,
source code written in a programming language like Swift or Objective-C.
That code is parsed into an abstract syntax tree,
(AST),
which is progressively transformed into
lower-level, intermediate representations
until it finally becomes executable binary.
What if,
instead of lowering source code down for the purpose of execution,
we raised source code for the purpose of understanding?
In this article,
I’d like to share an idea that I’ve been kicking around for a while.
It’s something that’s come into greater focus with
my recent work on swift-doc,
but first started to form during tenure in Apple Developer Publications,
back in 2015.
The idea is this: What if we took the lessons of the semantic web
and applied them to source code?
Specifically:
Representation:
Software components should be represented by
a common, language-agnostic data format.
Addressability:
Packages, modules, and their constituent APIs
should each have a unique URL identifier.
Decentralization:
Information should be distributed across a federated network of data sources,
which can cross-reference one another by URL.
I grew up with the Internet,
and got to see it, first-hand,
go from an obscure technology to the dominant cultural force.
So much of what I see in software development today
reminds me of what I remember about the web from 20 years ago.
And if you’ll forgive the extended wind-up,
I think there’s a lot we can learn by looking at that evolution.
Web 1.0 The Web of Documents
Tim Berners-Lee launched the World Wide Web
from a NeXT workstation 27 years ago.
His vision for a
globally-distributed, decentralized network of inter-connected documents
gave rise to the Internet as we know it today.
But it was also part of an intellectual tradition dating back to the 1940s,
which includes
Vannevar Bush’s Memex,
Ted Nelson’s Xanadu, and
Doug Engelbart’s Mother of All Demos.
In those early days,
the knowledge being shared was primarily academic.
As the userbase grew over time,
so too did the breadth and diversity of the information available.
And, for a time,
that’s what the Internet was:
fan sites for Sunbeam toasters,recipes for Neapolitan-style pizza, andthe official website for the 1996 film Space Jam.
But the web of documents had limits.
If you wanted to
shop for appliances,
see the menu of a pizza shop, or
get local showtimes for a movie,
you might be able to do that on the early Internet.
But you really had to work at it.
Back then,
you’d start by going to a directory like Yahoo! or DMOZ,
navigate to the relevant topic,
and click around until you found a promising lead.
Most of the time, you wouldn’t find what you were looking for;
instead, you’d disconnect your modem to free up your landline
and consult the yellow pages.
This started to change in the early ’00s.
Web 2.0 The Social Web
With Perl CGI and PHP,
you could now easily generate web pages on-the-fly.
This enabled eCommerce and the first commercial uses of the Internet.
After the ensuing dot-com bubble,
you had technologies like Java applets and Flash
bring a new level of interactivity to web sites.
Eventually, folks figured out how to usean obscure API from Internet Explorer 5
to replicate this interactivity on normal webpages —
a technique dubbed AJAX.
Interacting with a page and seeing results live, without reloading a page?
This was huge.
Without that,
social media might not have taken off as it did.
Anyway,
the server-side APIs powering those AJAX interactions on the client,
they were the secret sauce that let the Internet evolve into what it is today.
Thanks to all of these (often unsecured) AJAX endpoints,
developers could synthesize information across multiple sources
in ways that nobody had ever thought to do.
You could get someone’s location from Fire Eagle,
search for photos taken nearby on Flickr,
and use MOO to print and deliver prints of them on-demand.
By the end of the decade,
the rise of social networks and the early promise of mashups
started to coalesce into the modern Internet.
Web 3.0 The Web of Data
The term “Web 3.0” didn’t catch on like its predecessor,
but there’s a clear delineation between
the technologies and culture of the web between the early and late ’00s.
It’s hard to overstate how much the iPhone’s launch in 2007
totally changed the trajectory of the Internet.
But many other events played an important role in
shaping the web as we know it today:
Google acquiring the company behind Freebase,
giving it a knowledge graph to augment its website index.
Facebook launching Open Graph,
which meant everything could now be “Liked”
(and everyone could be targeted for advertisements).
Yahoo releasing SearchMonkey andBOSS,
two ambitious (albeit flawed) attempts
to carve out a niche from Google’s monopoly on search.
Wolfram launching Wolfram|Alpha,
which far exceeded what many of us thought was possible
for a question answering system.
The Internet always had a lot of information on it;
the difference now is that
the information is accessible to machines as well as humans.
Today,
you can ask Google“Who was the first person to land on the moon?”
and get an info box saying, “Commander Neil Armstrong”.
You can post a link in Messages
and see it represented bya rich visual summary
instead of a plain text URL.
You can ask Siri,“What is the airspeed velocity of an unladen swallow?” and hear back“I can’t get the answer to that on HomePod”About 25 miles per hour.
Think about what we take for granted about the Internet now,
and try to imagine doing that on the web when it lookedlike this.
It’s hard to think that any of this would be possible without the semantic web.
GitHub.com, Present Day The Spider and The Octocat
READMEs on GitHub.com today remind me of
personal home pages on Geocities back in the Web 1.0 days.
Even with the standard coat of paint,
you see an enormous degree of variance across projects and communities.
Some are sparse; others are replete with adornment.
And yet,
no matter what a project’s README looks like,
onboarding onto a new tool or library entails, well reading.
GitHub offers some structured informational cues:
language breakdown, license, some metadata about commit activity.
You can search within the repo using text terms.
And thanks to semantic / tree-sitter,
you can even click through to find declarations in some languages.
But where’s a list of methods?Where are the platform requirements?
You have to read the README to find out!
(Better hope it’s up-to-date 😭)
The modest capabilities of browsing and searching code today
more closely resemble AltaVista circa 2000 than Google circa 2020.
Theres so much more that we could be doing.
RDF Vocabularies The Owl and The Turtle
At the center of the semantic web is something calledRDF,
the Resource Description Framework.
It’s a collection of standards for representing and exchanging data.
The atomic data entity in RDF
is called a triple, which comprises:
a subject (“the sky”)
a predicate (“has the color”)
an object (“blue”)
You can organize triples according to avocabulary, or ontology,
which defines rules about how things are described.
RDF vocabularies are represented by the
Web Ontology Language
(OWL).
The ideas behind RDF are simple enough.
Often, the hardest part is navigating
its confusing, acronym-laden technology stack.
The important thing to keep in mind is that
information can be represented in several different ways
without changing the meaning of that information.
Let’s start to define a vocabulary for the Swift programming language.
To start,
we’ll define the concept of a
Symbol along with two subclasses, Structure and Function.
We’ll also define a name property that holds a token (a string)
that applies to any Symbol.
Finally,
we’ll define a returns property that applies to a Function
and holds a reference to another Symbol.
Encoding our knowledge into a standard format
lets anyone access that information — however they like.
And because these facts are encoded within an ontology,
they can be validated for coherence and consistency.
It’s totally language agnostic.
Querying the Results
With an RDF graph of facts,
we can query it using SPARQL.
Or,
we could load the information into
a graph database like Neo4j or
a relational database like PostgreSQL
and perform the query in Cypher or SQL, respectively.
“What can you do with a knowledge graph?”
That’s kind of like asking, “What can you do with Swift?”
The answer — “Pretty much anything”— is as true as it is unhelpful.
Perhaps a better framing would be to consider the kinds of questions that
a knowledge graph of code symbols can help answer:
Which methods in Foundation produce a Date value?
Which public types in my project don’t conform to Codable?
Which methods does Array inherit default implementations from RandomAccessCollection?
Which APIs have documentation that includes example code?
What are the most important APIs in MapKit?
Are there any unused APIs in my project?
What’s the oldest version of iOS that my app could target
based on my current API usage?
What APIs were added to Alamofire between versions 4.0 and 4.2?
What APIs in our app are affected by a CVE issued for a 3rd-party dependency?
The possibilities get even more interesting as you layer additional contexts
by linking Swift APIs to different domains and other programming languages:
How is this Swift API exposed in Objective-C?
Who are the developers maintaining the packages
that are pulled in as external dependencies for this project?
What’s the closest functional equivalent to this Swift package
that’s written in Rust?
Future Applications The Promise of What Lies Ahead
Any fact becomes important when it’s connected to another.
Umberto Eco, Foucault’s Pendulum
Operating on code symbolically is more powerful
than treating it as text.
Once you’ve experienced proper refactoring tools,
you’ll never want to go back to global find-and-replace.
The leap from symbolic to semantic understanding of code
promises to be just as powerful.
What follows are a few examples of potential applications of
the knowledge graph we’ve described.
Flexible Search Queries
GitHub’s advanced search
provides an interface to filter results on variousfacets,
but they’re limited to metadata about the projects.
You can search for Swift code written by
@kateinoigakukun in 2020,
but you can’t, for example,
filter for code compatible with Swift 5.1.
You can search code for the string “record”,
but you can’t disambiguate between type and function definitions
(class Record vs. func record()).
As we showed earlier,
the kinds of queries we can perform across a knowledge graph
are fundamentally different from what’s possible with
a conventional faceted, full-text search index.
For example,
here’s a SPARQL query to find the urls of repositories
created by @kateinoigakukun and updated this year
that contain Swift functions named record:
When faced withmissing or incomplete documentation,
developers are left to search Google for
blog posts, tutorials, conference videos, and sample code
to fill in the gaps.
Often, this means sifting through pages of irrelevant results —
to say nothing of outdated and incorrect information.
A knowledge graph can improve search for documentation
much the same as it can for code,
but we can go even further.
Similar to how academic papers contain citations,
example code can be annotated to include references to
the canonical APIs it interacts with.
Strong connections between references and its source material
make for easy retrieval later on.
Imagine if,
when you option-click on an API in Xcode
to get its documentation,
you also saw a list of sample code and WWDC session videos?
Or what if we could generate sample code automatically from test cases?
Wouldn’t that be nice?
All of that information is out there,
just waiting for us to connect the dots.
Automatic µDependencies
John D. Cook onceobserved,
code reuse is more like an organ transplant
than snapping LEGO blocks together.
Fred Brooks similarly analogized software developers to surgeons inThe Mythical Man-Month.
But that’s not to say that things can’t get better —
it’d be hard to argue that they haven’t.
Web applications were once described in similar, organic terms,
but that came to an end with the advent ofcontainerization.
Now you can orchestrate entire multi-cloud deployments automatically
via declarative configuration files.
Before [CPAN],
the state of the art for dependency management
was copy-pasting chunks of codeyou found on a web page.
But today, package managers are essential infrastructure for projects.
What if,
instead of organizing code into self-contained, modular chunks ourselves,
we let software do it for us?
Call itFaaD (Functions as a Dependency).
Say you want an implementation ofk-means clustering.
You might search around for “k-means” or “clustering” on GitHub
and find a package named “SwiftyClusterAlgorithms” (😒),
only to discover that it includes a bunch of functionality that you don’t need —
and to add insult to injury,
some of those extra bits happen to generate compiler warnings.
Super annoying.
Today, there’s no automatic way to pick and choose what you need.
(Swift import syntax (import func kMeans) is a lie)
But there’s no inherent reason why the compiler couldn’t do this for you.
Or to go even further:
If everything compiles down to web assembly,
there’s no inherent requirement for that implementation of k-means —
it could be written in Rust or JavaScript,
and you’d be none the wiser.
At a certain point,
you start to question the inherent necessity of software packaging
as we know it today.
Take it far enough,
and you may wonder how much code we’ll write ourselves in the future.
Code Generation
A few months ago,
Microsoft hosted its Build conference.
And among the videos presented was an interview withSam Altman,
CEO of OpenAI.
A few minutes in,
the interview cut to a video of Sam using
a fine-tuned version ofGPT-2
towrite Python code from docstrings.
defis_palindrome(s):"""Check whether a string is a palindrome"""returns==s[::-1]# ← Generated by AI model from docstring!
And that’s using a model that treats code as text.
Imagine how far you could go with a priori knowledge of programming languages!
Unlike English, the rules of code are, well, codified.
You can check to see if code compiles —
and if it does compile,
you can run it to see the results.
At this point,
you should feel either very worried or very excited.
If you don’t, then you’re not paying attention.
Taking Ideas Seriously The Shoemaker’s Children
The use of FORTRAN,
like the earlier symbolic programming,
was very slow to be taken up by the professionals.
And this is typical of almost all professional groups.
Doctors clearly do not follow the advice they give to others,
and they also have a high proportion of drug addicts.
Lawyers often do not leave decent wills when they die.
Almost all professionals are slow to use their own expertise for their own work.
The situation is nicely summarized by the old saying,“The shoe maker’s children go without shoes”.
Consider how in the future, when you are a great expert,
you will avoid this typical error!
Today,
lawyers delegate many paralegal tasks like document discovery to computers
and
doctors routinely use machine learning models to help diagnose patients.
So why aren’t we —
ostensibly the people writing software—
doing more with AI in our day-to-day?
Why are things like
TabNine andKite
so often seen as curiosities instead of game-changers?
If you take seriously the idea thatAI
will fundamentally change the nature of many occupations in the coming decade,
what reason do you have to believe that you’ll be immune from that
because you work in software?
Looking at the code you’ve been paid to write over the past few years,
how much of that can you honestly say is truly novel?
We’re really not as clever as we think we are.
Postscript Reflection and Metaprogramming
Today marks 8 years since I started NSHipster.
You might’ve noticed that I don’t write here as much as I once did.
And on the occasions that I do publish an article,
it’s more likely to include obscure
historical facts andcultural references
than to the obscure APIs promised by this blog’s tagline.
The truth is,
I’ve come around to thinking that
programming isn’t the most important thing
for programmers to pay attention to right now.
Anyway,
I’d like to take this opportunity to extend my sincere gratitude
to everyone who reads the words I write.
Thank you.
It may be a while before I get back into a regular cadence,
so apologies and in advance.
Until next time,May your code continue to compile and inspire.
Programming is about typing.
And programming languages are typically judged by how much they make you type —
in both senses of the word.
Swift is beloved for being able to save us a few keystrokes
without compromising safety or performance,
whether it’s through
implicit typing or
automatic synthesis of protocols like
Equatable andHashable.
But the OG
ergonomic feature of Swift is undoubtedly
automatic synthesis of RawRepresentable conformance
for enumerations with raw types.
You know…
the language feature that lets you do this:
enumGreeting:String{casehello="hello"casegoodbye// implicit raw value of "goodbye"}enumSortOrder:Int{caseascending=-1casesame// implicit raw value of 0casedescending// implicit raw value of 1}
Though “enum + RawValue” has been carved into the oak tree of our hearts
since first we laid eyes on that language with a fast bird,
few of us have had occasion to consider
what RawRepresentable means outside of autosynthesis.
This week,
we invite you to do a little extra typing
and explore some untypical use cases for the RawRepresentable protocol.
In Swift,
an enumeration can be declared withraw value syntax.
For any enumeration with a string, integer, or floating-point raw type,
the Swift compiler automatically adds RawRepresentable conformance.
When developers first start working with Swift,
they inevitably run into situations where raw value syntax doesn’t work:
Enumerations with raw values other than Int or String
Enumerations with associated values
Upon seeing those bright, red error sigils,
many of us fall back to a more conventional enumeration,
failing to realize that what we wanted to do wasn’t impossible,
but rather just slightly beyond what the compiler can do for us.
RawRepresentable with C Raw Value Types
The primary motivation for raw value enumerations is
to improve interoperability.
Quoting again from the docs:
Using the raw value of a conforming type
streamlines interoperation with Objective-C and legacy APIs.
This is true of Objective-C frameworks in the Apple SDK,
which declare enumerations with NS_ENUM.
But interoperability with other C libraries is often less seamless.
Consider the task of interfacing with libcmark,
a library for working with Markdown according to theCommonMark spec.
Among the imported data types is cmark_node_type,
which has the following C declaration:
typedefenum{/* Error status */CMARK_NODE_NONE,/* Block */CMARK_NODE_DOCUMENT,CMARK_NODE_BLOCK_QUOTE,…CMARK_NODE_HEADING,CMARK_NODE_THEMATIC_BREAK,CMARK_NODE_FIRST_BLOCK=CMARK_NODE_DOCUMENT,CMARK_NODE_LAST_BLOCK=CMARK_NODE_THEMATIC_BREAK,…}cmark_node_type;
We can immediately see a few details that would need to be ironed out
along the path of Swiftification —
notably,
1) the sentinel NONE value, which would instead be represented by nil, and
2) the aliases for the first and last block values,
which wouldn’t be encoded by distinct enumeration cases.
Attempting to declare a Swift enumeration
with a raw value type of cmark_node_type results in a compiler error.
enumNodeType:cmark_node_type{}// Error
However,
that doesn’t totally rule out cmark_node_type from being a RawValue type.
Here’s what we need to make that happen:
It’s a far cry from being able to say case document = CMARK_NODE_DOCUMENT,
but this approach offers a reasonable solution
that falls within the existing semantics of the Swift standard library.
That debunks the myth aboutInt and String being the only types that can be a raw value.
What about that one about associated values?
RawRepresentable and Associated Values
In Swift,
an enumeration case can have one or more associated values.
Associated values are a convenient way to introduce some flexibility
into the closed semantics of enumerations
and all the benefits they confer.
There are three numbers in computer science: 0, 1, and N.
enumNumber{casezerocaseonecasen(Int)}
Because of the associated value on n,
the compiler can’t automatically synthesize an Int raw value type.
But that doesn’t mean we can’t roll up our sleeves and pick up the slack.
What’s really interesting is that our contrived little enumeration type
benefits from the same, small memory footprint
that you get from using enumerations in more typical capacities:
Without built-in support for type unions
or an analog to the open access modifier for classes,
there’s nothing that an API provider can do,
for example,
to prevent a consumer from doing the following:
structAether:Elemental{}
Any switch statement over a type-erased Elemental value
using is checks will necessarily have a default case.
Until we have a first-class language feature for providing such guarantees,
we can recruit enumerations and raw values for a reasonable approximation:
Returning one last time to the docs,
we’re reminded that:
With a RawRepresentable type,
you can switch back and forth between
a custom type and an associated RawValue type
without losing the value of the original RawRepresentable type.
From the earliest days of the language, RawRepresentable has been relegated to
the thankless task of C interoperability.
But looking now with a fresh set of eyes,
we can now see it for in all its injective glory.
So the next time you find yourself with an enumeration
whose cases broker in discrete, defined counterparts,
consider adopting RawRepresentable to formalize the connection.
Last week,
Apple released the first beta of Xcode 11.4,
and it’s proving to be one of the most substantial updates in recent memory.
XCTest got a huge boost,
with numerous quality of life improvements,
and Simulator, likewise, got a solid dose ofTLC.
But it’s the changes to Swift that are getting the lion’s share of attention.
In Xcode 11.4,
Swift compile times are down across the board,
with many developers reporting improvements of 10 – 20% in their projects.
And thanks to a new diagnostics architecture,
error messages from the compiler are consistently more helpful.
This is also the first version of Xcode to ship with the newsourcekit-lsp server,
which serves to empower editors like VSCode
to work with Swift in a more meaningful way.
Yet,
despite all of these improvements
(which are truly an incredible achievement by Apple’s Developer Tools team),
much of the early feedback has focused on
the most visible additions to Swift 5.2.
And the response from the peanut galleries of
Twitter, Hacker News, and Reddit has been —
to put it charitably — “mixed”.
Granted,
both of those examples are terrible.
And that’s kinda the problem.
Too often,
coverage of “What’s New In Swift”
amounts to little more than a regurgitation of Swift Evolution proposals,
interspersed with poorly motivated (and often emoji-laden) examples.
Such treatments provide a poor characterization of Swift language features,
and — in the case of Swift 5.2 —
serves to feed into the popular critique that these are frivolous additions —
mere syntactic sugar.
This week,
we hope to reach the ooey gooey center of the issue
by providing some historical and theoretical context
for understanding these new features.
Syntactic Sugar in Swift
If you’re salty about “key path as function” being too sugary,
recall that the status quo
isn’t without a sweet tooth.
Consider our saccharine example from before:
"🧁🍭🍦".unicodeScalars.map{$0.properties.name}
That expression relies on at least four different syntactic concessions:
Trailing closure syntax,
which allows a final closure argument label of a function to be omitted
Anonymous closure arguments,
which allow arguments in closures to be used positionally ($0, $1, …)
without binding to a named variable.
Inferred parameter and return value types
Implicit return from single-expression closures
If you wanted to cut sugar out of your diet completely,
you’d best get Mavis Beacon on the line,
because you’ll be doing a lot more typing.
In fact,
as we’ll see in the examples to come,
Swift is a marshmallow world in the winter,
syntactically speaking.
From initializers and method calls to optionals and method chaining,
nearly everything about Swift could be described as a cotton candy melody —
it really just depends on where you draw the line between
“language feature” and “syntactic sugar”.
To understand why,
you have to understand how we got here in the first place,
which requires a bit of history, math, and computer science.
Get ready to eat your vegetables 🥦.
The λ-Calculus and Speculative Computer Science Fiction
All programming languages can be seen as various attempts to representthe λ-calculus.
Everything you need to write code —
variables, binding, application —
it’s all in there,
buried under a mass of Greek letters and mathematical notation.
Setting aside syntactic differences,
each programming language can be understood by
its combination of affordances for
making programs easier to write and easier to read.
Language features like
objects,
classes,
modules,
optionals,
literals,
and generics
are all just abstractions built on top of the λ-calculus.
Here you get a glimpse into three very different timelines;
ours is the reality in which ALGOL’s syntax (option #2)
“won out” over the alternatives.
From ALGOL 60,
you can draw a straight line from
CPL in 1963,
to BCPL in 1967
and C in 1972,
followed by Objective-C in 1984
and Swift in 2014.
That’s the lineage that informs what types are callable and how we call them.
Now, back to Swift…
Function Types in Swift
Functions are first-class objects in Swift,
meaning that they can be assigned to variables,
stored in properties,
and passed as arguments or returned as values from other functions.
What distinguishes function types from other values
is that they’re callable,
meaning that you can invoke them to produce new values.
Closures
Swift’s fundamental function type is the closure,
a self-contained unit of functionality.
letsquare:(Int)->Int={xinx*x}
As a function type,
you can call a closure by passing the requisite number of arguments
between opening and closing parentheses ()—
a la ALGOL.
square(4)// 16
Closures are so called because they close over and capture
references to any variables from the context in which they’re defined.
However, capturing semantics aren’t always desirable,
which is why Swift provides dedicated syntax to a special kind of closure
known as a function.
Functions
Functions defined at a top-level / global scope
are named closures that don’t capture any values.
In Swift,
you declare them with the func keyword:
funcsquare(_x:Int)->Int{x*x}square(4)// 16
Compared to closures,
functions have greater flexibility in how arguments are passed.
Function arguments can have named labels
instead of a closure’s unlabeled, positional arguments —
which goes a long way to clarify the effect of code at its call site:
Functions can also take variadic arguments,
implicit closures,
and default argument values
(allowing for magic expression literals like #file and #line):
And yet,
despite all of this flexibility for accepting arguments,
most functions you’ll encounter operate on an implicitself argument.
These functions are called methods.
Methods
A method is a function contained by a type.
Methods automatically provide access to self,
allowing them to effectively capture the instance on which they’re called
as an implicit argument.
Compared to more verbose languages like Objective-C,
the experience of writing Swift is, well, pretty sweet.
It’s hard to imagine any Swift developers objecting to what we have here
as being “sugar-coated”.
But like a 16oz can of Surge,
the sugar content of something is often surprising.
Turns out,
that example from before is far from innocent:
varqueue=Queue<Int>()// desugars to `Queue<Int>.init()`queue.push(1)// desugars to `Queue.push(&queue)(1)`
With this in mind,
let’s now take another look at callable types in Swift more generally.
{Type, Instance, Member} ⨯ {Static, Dynamic}
Since their introduction in Swift 4.2 and Swift 5, respectively,
many developers have had a hard time keeping@dynamicMemberLookup and @dynamicCallable
straight in their minds —
made even more difficult by the introduction of callAsFunction in Swift 5.2.
If you’re also confused,
we think the following table can help clear things up:
Static
Dynamic
Type
init
N/A
Instance
callAsFunction
@dynamicCallable
Member
func
@dynamicMemberLookup
Swift has always had static callable types and type members.
What’s changed in new versions of Swift
is that instances are now callable,
and both instances and members can now be called dynamically.
Let’s see what that means in practice,
starting with static callables.
This type can be called statically in the following ways:
letinstance=Static()// ❶ desugars to `Static.init()`Static.function()// ❷ (no syntactic sugar!)instance.function()// ❸ desugars to Static.function(instance)()instance()// ❹ desugars to `Static.callAsFunction(instance)()`
❶
Calling the Static type invokes an initializer
❷
Calling function on the Static type
invokes the corresponding static function member,
passing Static as an implicit self argument.
❸
Calling function on an instance of Static
invokes the corresponding function member,
passing the instance as an implicit self argument.
❹
Calling an instance of Static
invokes the callAsFunction() function member,
passing the instance as an implicit self argument.
This type can be called dynamically in a few different ways:
letinstance=Dynamic()// desugars to `Dynamic.init()`instance(1)// ❶ desugars to `Dynamic.dynamicallyCall(instance)(withArguments: [1])`instance(a:1)// ❷ desugars to `Dynamic.dynamicallyCall(instance)(withKeywordArguments: ["a": 1])`Dynamic.function(1)// ❸ desugars to `Dynamic[dynamicMember: "function"](1)`instance.function(1)// ❹ desugars to `instance[dynamicMember: "function"](1)`
❶
Calling an instance of Dynamic
invokes the dynamicallyCall(withArguments:) method,
passing an array of arguments
and Dynamic as an implicit self argument.
❷
Calling an instance of Dynamic
with at least one labeled argument
invokes the dynamicallyCall(withKeywordArguments:) method,
passing the arguments in a KeyValuePairs object
and Dynamic as an implicit self argument.
❸
Calling function on the Dynamic type
invokes the static dynamicMember subscript,
passing "function" as the key;
here, we call the returned anonymous closure.
❹
Calling function on an instance of Dynamic
invokes the dynamicMember subscript,
passing "function" as the key;
here, we call the returned anonymous closure.
Dynamism by Declaration Attributes
@dynamicCallable and @dynamicMemberLookup
are declaration attributes,
which means that they can’t be applied to existing declarations
through an extension.
So you can’t, for example,
spice upInt with Ruby-ish
natural language accessors:
@dynamicMemberLookup// ⚠︎ Error: '@dynamicMemberLookup' attribute cannot be applied to this declarationextensionInt{staticsubscript(dynamicMembermember:String)->Int?{letstring=member.replacingOccurrences(of:"_",with:"-")letformatter=NumberFormatter()formatter.numberStyle=.spellOutreturnformatter.number(from:string)?.intValue}}// ⚠︎ Error: Just to be super clear, this doesn't workInt.forty_two// 42 (hypothetically, if we could apply `@dynamicMemberLookup` in an extension)
Contrast this with callAsFunction,
which can be added to any type in an extension.
There’s much more to talk about with
@dynamicMemberLookup, @dynamicCallable, and callAsFunction,
and we look forward to covering them all in more detail
in future articles.
Sometimes to ship software,
you need to pair up and “ship” different technologies.
In building these features,
the “powers that be” have ordained that
Swift replace Python for Machine Learning.
Taking for granted that an incremental approach is best,
the way to make that happen is to allow
Swift to interoperate with Python
as seamlessly as it does with Objective-C.
And since Swift 4.2,
we’ve been getting pretty close.
The promise of additive changes is that they don’t change anything
if you don’t want them to.
You can continue to write Swift code
remaining totally ignorant of the features described in this article
(most of us have so far).
But let’s be clear:
there are no cost-free abstractions.
Economics uses the term negative externalities
to describe indirect costs incurred by a decision.
Although you don’t pay for these features unless you use them,
we all shoulder the burden of a more complex language
that’s more difficult to teach, learn, document, and reason about.
A lot of us who have been with Swift from the beginning
have grown weary of Swift Evolution.
And for those on the outside looking in,
it’s unfathomable that we’re wasting time on inconsequential “sugar” like this
instead of features that will really move the needle,
like async / await.
In isolation,
each of these proposals is thoughtful and useful — genuinely.
We’ve already had occasion to use a few of them.
But it can be really hard to judge things on their own technical merits
when they’re steeped in emotional baggage.
Everyone has their own sugar tolerance,
and it’s often informed by what they’re accustomed to.
Being cognizant of the drawbridge effect,
I honestly can’t tell if I’m out of touch,
or if it’s the children who are wrong…