In Japan,
there’s a comedy tradition known as
Manzai (漫才).
It’s kind of a cross between stand up and vaudeville,
with a straight man and a funny man
delivering rapid-fire jokes that revolve around miscommunication and wordplay.
As it were, we’ve been working on a new routine
as a way to introduce the subject for this week’s article, CharacterSet,
and wanted to see what you thought:
Is CharacterSet a Set<Character>?
Of course not!
What about NSCharacterSet?
That's an old reference.
Then what would you call a collection of characters?
That would be a String!
(╯° 益 °)╯ 彡 ┻━┻
(Yeah, we might need to workshop this one a bit more.)
All kidding aside,
CharacterSet is indeed ripe for miscommunication and wordplay (so to speak):
it doesn’t store Character values,
and it’s not a Set in the literal sense.
So what is CharacterSet and how can we use it?
Let’s find out! (行きましょう!)
CharacterSet (and its reference type counterpart, NSCharacterSet)
is a Foundation type used to trim, filter, and search for
characters in text.
In Swift,
a Character is an extended grapheme cluster
(really just a String with a length of 1)
that comprises one or more scalar values.
CharacterSet stores those underlying Unicode.Scalar values,
rather than Character values, as the name might imply.
The “set” part of CharacterSet
refers not to Set from the Swift standard library,
but instead to the SetAlgebra protocol,
which bestows the type with the same interface:
contains(_:), insert(_:), union(_:), intersection(_:), and so on.
Predefined Character Sets
CharacterSet defines constants
for sets of characters that you’re likely to work with,
such as letters, numbers, punctuation, and whitespace.
Most of them are self-explanatory and,
with only a few exceptions,
correspond to one or more
Unicode General Categories.
Type Property
Unicode General Categories & Code Points
alphanumerics
L*, M*, N*
letters
L*, M*
capitalizedLetters*
Lt
lowercaseLetters
Ll
uppercaseLetters
Lu, Lt
nonBaseCharacters
M*
decimalDigits
Nd
punctuationCharacters
P*
symbols
S*
whitespaces
Zs, U+0009
newlines
U+000A – U+000D, U+0085, U+2028, U+2029
whitespacesAndNewlines
Z*, U+000A – U+000D, U+0085
controlCharacters
Cc, Cf
illegalCharacters
Cn
The remaining predefined character set, decomposables,
is derived from the
decomposition type and mapping
of characters.
Trimming Leading and Trailing Whitespace
Perhaps the most common use for CharacterSet
is to remove leading and trailing whitespace from text.
You can use this, for example,
when sanitizing user input or preprocessing text.
Predefined URL Component Character Sets
In addition to the aforementioned constants,
CharacterSet provides predefined values
that correspond to the characters allowed in various
components of a URL:
urlUserAllowed
urlPasswordAllowed
urlHostAllowed
urlPathAllowed
urlQueryAllowed
urlFragmentAllowed
Escaping Special Characters in URLs
Only certain characters are allowed in certain parts of a URL
without first being escaped.
For example, spaces must be percent-encoded as %20 (or +)
when part of a query string like
https://nshipster.com/search/?q=character%20set.
URLComponents takes care of percent-encoding components automatically,
but you can replicate this functionality yourself
using the addingPercentEncoding(withAllowedCharacters:) method
and passing the appropriate character set:
In addition to these predefined character sets,
you can create your own.
Build them up character by character,
inserting multiple characters at a time by passing a string,
or by mixing and matching any of the predefined sets.
Validating User Input
You might create a CharacterSet to validate some user input to, for example,
allow only lowercase and uppercase letters, digits, and certain punctuation.
Depending on your use case,
you might find it easier to think in terms of what shouldn’t be allowed,
in which case you can compute the inverse character set
using the inverted property:
If a CharacterSet is created as the result of an expensive operation,
you may consider caching its bitmapRepresentation
for later reuse.
For example,
if you wanted to create CharacterSet for Emoji,
you might do so by enumerating over the Unicode code space (U+0000 – U+1F0000)
and inserting the scalar values for any characters with
Emoji properties
using the properties property added in Swift 5 by
SE-0221 “Character Properties”:
importFoundationvaremoji=CharacterSet()forcodePointin0x0000...0x1F0000{guardletscalarValue=Unicode.Scalar(codePoint)else{continue}// Implemented in Swift 5 (SE-0221)// https://github.com/apple/swift-evolution/blob/master/proposals/0221-character-properties.mdifscalarValue.properties.isEmoji{emoji.insert(scalarValue)}}
The resulting bitmapRepresentation is a 16KB Data object.
emoji.bitmapRepresentation// 16385 bytes
You could store that in a file somewhere in your app bundle,
or embed its Base64 encoding
as a string literal directly in the source code itself.
In this season of giving,
let’s stop to consider one of the greatest gifts given to us
by modern computer systems:
the gift of abstraction.
Consider those billions of people around the world
who use computers and mobile devices on a daily basis.
They do this without having to know anything about
the millions of CPU transistors and SSD sectors and LCD pixels
that come together to make that happen.
All of this is thanks to abstractions
like files and directories and apps and documents.
This week on NSHipster,
we’ll be talking about two important abstractions on Apple platforms:
bundles and packages.
🎁
Despite being distinct concepts,
the terms “bundle” and “package” are frequently used interchangeably.
Part of this is undoubtedly due to their similar names,
but perhaps the main source of confusion is that
many bundles just so happen to be packages (and vice versa).
So before we go any further,
let’s define our terminology:
A bundle is a directory with a known structure
that contains executable code and the resources that code uses.
A package is a directory
that looks like a file when viewed in Finder.
The following diagram illustrates the relationship between bundles and packages,
as well as things like apps, frameworks, plugins, and documents
that fall into either or both categories:
Bundles are primarily for improving developer experience
by providing structure for organizing code and resources.
This structure not only allows for predictable loading of code and resources
but allows for system-wide features like localization.
Bundles fall into one of the following three categories,
each with their own particular structure and requirements:
App Bundles, which contain an executable that can be launched,
an Info.plist file describing the executable,
app icons, launch images,
and other assets and resources used by the executable, including
interface files, strings files, and data files.
Framework Bundles, which contain
code and resources used by the dynamic shared library.
Loadable Bundles like plug-ins, which contain
executable code and resources that extend the functionality of an app.
In apps, playgrounds, and most other contexts
the bundle you’re interested in is accessible through
the type property Bundle.main.
And most of the time,
you’ll use url(forResource:withExtension:)
(or one of its variants)
to get the location of a particular resource.
For example,
if your app bundle includes a file named Photo.jpg,
you can get a URL to access it like so:
For everything else,
Bundle provides several instance methods and properties
that give the location of standard bundle items,
with variants returning either a URL or a String paths:
You can get any other information
by subscript access to the infoDictionary property.
(Or if that information is presented to the user,
use the localizedInfoDictionary property instead).
One of the most important features that bundles facilitate is localization.
By enforcing a convention for where localized assets are located,
the system can abstract the logic
for determining which version of a file to load
away from the developer.
For example,
bundles are responsible for loading the localized strings used by your app.
You can access them using the localizedString(forKey:value:table:) method.
However, it’s almost always a better idea to use
NSLocalizedString so that utilities like genstrings
can automatically extract keys and comments to .strings files for translation.
In Finder,
you can control-click to show a contextual menu
with actions to perform on a selected item.
If an item is a package,
“Show Package Contents” will appear at the top,
under “Open”.
Selecting this menu item will open a new Finder window
from the package directory.
You can, of course,
access the contents of a package programmatically, too.
The best option depends on the kind of package:
If a package has bundle structure,
it’s usually easiest to use
Bundle
as described in the previous section.
If a package is a document, you can use
NSDocument on macOS
and UIDocument on iOS.
Otherwise, you can use
FileWrapper
to navigate directories, files, and symbolic links,
and FileHandler
to read and write to file descriptors.
Although it’s up to the Finder how it wants to represent files and directories,
most of that is delegated to the operating system
and the services responsible for managing
Uniform Type Identifiers (UTI).
If you want to determine whether a file extension
is one of the built-in system package types
or used by an installed app as a registered document type,
you can use the Core Services functions
UTTypeCreatePreferredIdentifierForTag(_:_:_:) and
UTTypeConformsTo(_:_:):
As we’ve seen,
it’s not just end-users that benefit from abstractions —
whether it’s the safety and expressiveness of
a high-level programming language like Swift
or the convenience of APIs like Foundation,
we as developers leverage abstraction to make great software.
For all that we may (rightfully) complain
about abstractions that are
leaky or
inverted,
it’s important to take a step back
and realize how many useful abstractions we deal with every day,
and how much they allow us to do.
Objective-C required us to
wax philosophic
about the nature of equality and identity.
To the relief of any developer less inclined towards discursive treatises,
this is not as much the case for Swift.
In Swift,
there’s the Equatable protocol,
which explicitly defines the semantics of equality and inequality
in a manner entirely separate from the question of identity.
There’s also the Comparable protocol,
which builds on Equatable to refine inequality semantics
to creating an ordering of values.
Together, the Equatable and Comparable protocols
form the central point of comparison throughout the language.
Values conforming to the Equatable protocol
can be evaluated for equality and inequality.
Conformance to Equatable requires
the implementation of the equality operator (==).
As an example,
consider the following
Binomen structure:
Well actually, it’s even easier than that —
as of Swift 4.1,
the compiler can automatically synthesize conformance
for structures whose stored properties all have types that are Equatable.
We could replace all of the code in the extension
by simply adopting Equatable in the declaration of Binomen:
Equatability isn’t just about using the == operator —
there’s also the != operator!
it also lets a value,
among other things,
be found in a collection and
matched in a switch statement.
Equatable is also a requirement for conformance to
Hashable,
another important type in Swift.
This is all to say that
if a type has equality semantics —
if two values of that type can be considered equal or unequal –
it should conform to Equatable.
The Swift standard library and most of the frameworks in Apple SDKs
do a great job adopting Equatable for types that make sense to be.
So, in practice, you’re unlikely to be in a situation
where the dereliction of a built-in type
spoils automatic synthesis for your own type.
Instead, the most common obstacle to automatic synthesis involves tuples.
Consider this poorly-considered
Trinomen type:
structTrinomen{letgenus:Stringletspecies:(String,subspecies:String?)// 🤔}extensionTrinomen:Equatable{}// 🛑 Type 'Trinomen' does not conform to protocol 'Equatable'
As described in our article about Void,
tuples aren’t nominal types,
so they can’t conform to Equatable.
If you wanted to compare two trinomina for equality,
you’d have to write the conformance code for Equatable…like some kind of animal.
In addition to automatic synthesis of Equatable,
Swift 4.1 added another critical feature:
conditional conformance.
To illustrate this,
consider the following generic type
that represents a quantity of something:
structQuantity<Thing>{letcount:Intletthing:Thing}
Can Quantity conform to Equatable?
We know that integers are equatable,
so it really depends on what kind of Thing we’re talking about.
What conditional conformance Swift 4.1 allows us to do is
create an extension on a type with a conditional clause.
We can use that here to programmatically express that
_“a quantity of a thing is equatable if the thing itself is equatable”:
extensionQuantity:EquatablewhereThing:Equatable{}
And with that declaration alone,
Swift has everything it needs to synthesize conditional Equatable conformance,
allowing us to do the following:
For reference types,
the notion of equality becomes conflated with identity.
It makes sense that two Name structures with the same values would be equal,
but two Person objects can have the same name and still be different people.
For Objective-C-compatible object types,
the == operator is already provided from the isEqual: method:
That said,
Equatable semantics for reference types
are often not as straightforward as a straight identity check,
so before you add conformance to all of your classes,
ask yourself whether it actually makes sense to do so.
Building on Equatable,
the Comparable protocol allows for values to be considered
less than or greater than other values.
Comparable requires implementations for the following operators:
Operator
Name
<
Less than
<=
Less than or equal to
>=
Greater than or equal to
>
Greater than
…so it’s surprising that you can get away with only implementing one of them:
the < operator.
Going back to our binomial nomenclature example,
let’s extend Binomen to conform to Comparable
such that values are ordered alphabetically
first by their genus name and then by their species name:
This is quite clever.
Since the implementations of each comparison operator
can be derived from just < and ==,
all of that functionality is made available automatically through type inference.
Unlike Equatable,
the Swift compiler can’t automatically synthesize conformance to Comparable.
But that’s not for lack of trying — it’s just not possible.
There are no implicit semantics for comparability
that could be derived from the types of stored properties.
If a type has more than one stored property,
there’s no way to determine how they’re compared relative to one another.
And even if a type had only a single property whose type was Comparable,
there’s no guarantee how the ordering of that property
would relate to the ordering of the value as a whole
In the Swift standard library,
Equatable is a type without an equal;
Comparable a protocol without compare.
Take care to adopt them in your own types as appropriate
and you’ll benefit greatly.
One of the first lessons we learn as software developers
is how to organize concepts and functionality into discrete units.
At the smallest level,
this means thinking about types and methods and properties.
These pieces then form the basis of one or more modules,
which may then be packaged into libraries or frameworks.
In this way,
import declarations are the glue that holds everything together.
Yet despite their importance,
most Swift developers are familiar only with their most basic form:
import<#module#>
This week on NSHipster,
we’ll explore the other shapes
of this most prominent part of Swift.
An import declaration allows your code to access symbols
that are declared in other files.
However, if more than one module
declares a function or type with the same name,
the compiler may not be able to tell which one you want to call in code.
To demonstrate this,
consider two modules representing the multisport competitions of
Triathlon and
Pentathlon:
A triathlon consists of three events:
swimming, cycling, and running.
The modern pentathlon comprises five events:
fencing, swimming, equestrian, shooting, and running.
// Pentathlon Modulefuncfence(){print("🤺 Bout with épées")}funcswim(){print("🏊 Swim 200 m")}funcride(){print("🏇 Complete a show jumping course")}funcshoot(){print("🎯 Shoot 5 targets")}funcrun(){print("🏃 Run 3 km cross-country")}
If we import either of the modules individually,
we can reference each of their functions
using their unqualified names without a problem.
But if we import both modules together,
we can’t always use unqualified function names.
Triathlon and Pentathlon both include swimming and running,
so a reference to swim() is ambiguous.
How do we reconcile this?
One strategy is to use
fully-qualified names
to work around any ambiguous references.
By including the module name,
there’s no confusion about whether the program will swim
a few laps in a pool or a mile in open water.
importTriathlonimportPentathlonTriathlon.swim()// OK, fully-qualified reference to Triathlon.swimPentathlon.swim()// OK, fully-qualified reference to Pentathlon.swim
Another way to resolve API name collision
is to change the import declaration
to be more selective about what’s included from each module.
Import declarations have a form
that can specify individual
structures, classes, enumerations, protocols, and type aliases
as well as functions, constants, and variables declared at the top-level:
import<#kind#><#module.symbol#>
Here,
kind can be any of the following keywords:
Kind
Description
struct
Structure
class
Class
enum
Enumeration
protocol
Protocol
typealias
Type Alias
func
Function
let
Constant
var
Variable
For example,
the following import declaration adds only the swim() function
from the Pentathlon module:
When multiple symbols are referenced by the same name in code,
the Swift compiler resolves this reference
by consulting the following, in order:
Local Declarations
Imported Declarations
Imported Modules
If any of these have more than one candidate,
Swift is unable to resolve the ambiguity
and raises a compilation error.
For example,
importing the Triathlon module
provides the swim(), bike(), and run() methods.
The imported swim() function declaration from the Pentathlon
overrides that of the Triathlon module.
Likewise, the locally-declared run() function
overrides the symbol by the same name from Triathlon,
and would also override any imported function declarations.
importTriathlonimportfuncPentathlon.swim// Local function shadows whole-module import of Triathlonfuncrun(){print("🏃 Run 42.195 km")}swim()// OK, calls Pentathlon.swimbike()// OK, calls Triathlon.bikerun()// OK, calls local run
The result of calling this code?
A bizarre multi-sport event involving
a few laps in the pool,
a modest bike ride,
and a marathon run.
(@ us, IRONMAN)
This technique can be especially helpful
when importing top-level constants and variables,
whose provenance is often more difficult to discern
than other imported symbols.
For example,
the Darwin framework exports —
among other things —
a top-level stderr variable.
An explicit import declaration here
can preempt any questions during code review
about where that variable is coming from.
The final form of import declarations
offers another way to limit API exposure:
import<#module.submodule#>
You’re most likely to encounter submodules
in large system frameworks like AppKit and Accelerate.
These umbrella frameworks
are no longer considered a best-practice,
but they served an important role during
Apple’s transition to Cocoa in the early 00’s.
For example,
you might import only the
DictionaryServices
submodule from the
Core Services framework
to insulate your code from the myriad deprecated APIs
like Carbon Core.
importFoundationimportCoreServices.DictionaryServicesfuncdefine(_word:String)->String?{letnsstring=wordasNSStringletcfrange=CFRange(location:0,length:nsstring.length)guardletdefinition=DCSCopyTextDefinition(nil,nsstring,cfrange)else{returnnil}returnString(definition.takeUnretainedValue())}define("apple")// "apple | ˈapəl | noun 1 the round fruit of a tree..."
In practice,
isolating imported declarations and submodules
doesn’t confer any real benefit beyond signaling programmer intent.
Your code won’t compile any faster doing it this way.
And since most submodules seem to to re-import their umbrella header,
this approach won’t do anything to reduce noise in autocomplete lists.
Like many obscure and advanced topics,
the most likely reason you haven’t heard about
these import declaration forms before
is that you don’t need to know about them.
If you’ve gotten this far making apps without them,
you can be reasonably assured that you don’t need to start using them now.
Rather, the valuable takeaway here is understanding
how the Swift compiler resolves name collisions.
And to that end,
import declarations are a concept of great import.
This week’s article is about dictionaries.
No, not the Dictionary / NSDictionary / CFDictionaryRef
we encounter every day,
but rather those distant lexicographic vestiges of school days past.
Though widely usurped of their ‘go-to reference’ status by the Internet,
dictionaries and word lists serve an important role
behind the scenes for features ranging from
spell check, grammar check, and auto-correct to
auto-summarization and semantic analysis.
So, for your reference,
here’s a look at the ways and means by which
computers give meaning to the world through words,
in Unix, macOS, and iOS.
Symlinked to words is the web2 word list,
which —
though not exhaustive —
is still a sizable corpus:
$wc /usr/share/dict/words
235886 235886 2493109
Skimming with head shows what fun lies herein.
Such excitement is rarely so palpable as it is among words beginning with “a”:
$head /usr/share/dict/words
A
a
aa
aal
aalii
aam
Aani
aardvark
aardwolf
Aaron
These giant, system-provided text files make it easy to
grep crossword puzzle clues,
generate mnemonic passphrases, and
seed databases.
But from a user perspective,
/usr/share/dict’s monolingualism
and lack of associated meaning
make it less than helpful for everyday use.
macOS builds upon this with its own system dictionaries.
Never one to disappoint,
the operating system’s penchant for extending Unix functionality
by way of strategically placed bundles and plist files
is in full force here with how dictionaries.
The macOS analog to /usr/share/dict can be found in /Library/Dictionaries.
A quick peek into the shared system dictionaries
demonstrates one immediate improvement over Unix:
acknowledging the existence of languages other than English:
$ls /Library/Dictionaries/
Apple Dictionary.dictionary/
Diccionario General de la Lengua Española Vox.dictionary/
Duden Dictionary Data Set I.dictionary/
Dutch.dictionary/
Italian.dictionary/
Korean - English.dictionary/
Korean.dictionary/
Multidictionnaire de la langue française.dictionary/
New Oxford American Dictionary.dictionary/
Oxford American Writer's Thesaurus.dictionary/
Oxford Dictionary of English.dictionary/
Oxford Thesaurus of English.dictionary/
Sanseido Super Daijirin.dictionary/
Sanseido The WISDOM English-Japanese Japanese-English Dictionary.dictionary/
Simplified Chinese - English.dictionary/
The Standard Dictionary of Contemporary Chinese.dictionary/
macOS ships with dictionaries in
Chinese, English, French, Dutch, Italian, Japanese, and Korean,
as well as an English thesaurus
and a special dictionary for Apple-specific terminology.
Diving deeper into the rabbit hole,
we peruse the .dictionary bundles to see them for what they really are:
Proprietary binary encoding like this would usually be
the end of the road in terms of what one could reasonably do with data.
Luckily, Core Services provides APIs to read this information.
To get the definition of a word on macOS,
one can use the DCSCopyTextDefinition function
found in the Core Services framework:
importFoundationimportCoreServices.DictionaryServicesfuncdefine(_word:String)->String?{letnsstring=wordasNSStringletcfrange=CFRange(location:0,length:nsstring.length)guardletdefinition=DCSCopyTextDefinition(nil,nsstring,cfrange)else{returnnil}returnString(definition.takeUnretainedValue())}define("apple")// "apple | ˈapəl | noun 1 the round fruit of a tree..."
Wait, where did all of those great dictionaries go?
Well, they all disappeared into that first NULL argument.
One might expect to provide a DCSCopyTextDefinition type here —
as prescribed by the function definition.
However, there are no public functions to construct or copy such a type,
making nil the only available option.
The documentation is as clear as it is stern:
This parameter is reserved for future use, so pass NULL.
Dictionary Services searches in all active dictionaries.
“Dictionary Services searches in all active dictionaries”, you say?
Sounds like a loophole!
Now, there’s nothing programmers love to hate to love more than
exploiting loopholes to side-step Apple platform restrictions.
Behold: an entirely error-prone approach to getting, say, thesaurus results
instead of the first definition available in the standard dictionary:
NSUserDefaults*userDefaults=[NSUserDefaultsstandardUserDefaults];NSMutableDictionary*dictionaryPreferences=[[userDefaultspersistentDomainForName:@"com.apple.DictionaryServices"]mutableCopy];NSArray*activeDictionaries=[dictionaryPreferencesobjectForKey:@"DCSActiveDictionaries"];dictionaryPreferences[@"DCSActiveDictionaries"]=@[@"/Library/Dictionaries/Oxford American Writer's Thesaurus.dictionary"];[userDefaultssetPersistentDomain:dictionaryPreferencesforName:@"com.apple.DictionaryServices"];{NSString*word=@"apple";NSString*definition=(__bridge_transferNSString*)DCSCopyTextDefinition(NULL,(__bridgeCFStringRef)word,CFRangeMake(0,[wordlength]));NSLog(@"%@",definition);}dictionaryPreferences[@"DCSActiveDictionaries"]=activeDictionaries;[userDefaultssetPersistentDomain:dictionaryPreferencesforName:@"com.apple.DictionaryServices"];
“But this is macOS,
a platform whose manifest destiny
cannot be contained by meager sandboxing attempts from Cupertino!”,
you cry.
“Isn’t there a more civilized approach? Like, say, private APIs?”
Not publicly exposed but still available through Core Services
are a number of functions that cut closer to
the dictionary services functionality we crave:
With instances of the elusive DCSDictionaryRef type available at our disposal,
we can now see what all of the fuss is about with
that first argument in DCSCopyTextDefinition:
Most surprising from this experimentation
is the ability to access the raw HTML for entries,
which — combined with a dictionary’s bundled CSS —
produces the result seen in Dictionary.app.
iOS development is a decidedly more by-the-books affair,
so attempting to reverse-engineer the platform
would be little more than an academic exercise.
Fortunately, a good chunk of functionality is available
through an obscure UIKit class, UIReferenceLibraryViewController.
UIReferenceLibraryViewController is similar to an
MFMessageComposeViewController in that provides
a minimally-configurable view controller around system functionality
that’s intended to present modally.
This is the same behavior that one might encounter
when tapping the “Define” menu item on a highlighted word in a text view.
UIReferenceLibraryViewController also provides
the class method dictionaryHasDefinitionForTerm:.
A developer would do well to call this
before presenting a dictionary view controller
that will inevitably have nothing to display.
From Unix word lists to their evolved .dictionary bundles on macOS
(and presumably iOS, too),
words are as essential to application programming
as mathematical constants and the
“Sosumi” alert noise.
Consider how the aforementioned APIs can be integrated into your own app,
or used to create a kind of app you hadn’t previously considered.
Swift is a fast, safe, modern programming language
with an open governance model and a vibrant community.
There’s no reason that it should be limited to just making apps.
And indeed,
many smart people are working hard to
bring Swift to new platforms
and evolve its capabilities for
web development and
machine learning.
Scripting is another point of interest for the Swift community,
but the amount of setup required to incorporate 3rd-party dependencies
has long been seen as a non-starter.
…that is, until now.
On Friday,
Max Howellannounced
a new project called
swift-sh.
The project provides a shim around the Swift compiler
that creates a package for your script
and uses it to automatically add and manage external dependencies
imported with a special trailing comment.
Although in its initial stages of development,
it’s already seemingly managed to solve the biggest obstacle
to Swift from becoming a productive scripting language.
This week,
let’s take a very early look at this promising new library
and learn how to start using it today to write Swift scripts.
The original Swift Package Manager examples
provided a
shuffleableDeck of
PlayingCard values.
So it feels appropriate to revisit them here.
For this example,
let’s build a Swift script to deal and print out a formatted representation of
a hand of Bridge.
The final result should look something like this:
The comment after the import declaration for DeckOfPlayingCards
tells swift-sh to look for the package
on GitHub in a repository by the same name
under the NSHipster username.
The tilde-greater-than operator in ~> 4.0.0
is shorthand for specifying a version
“equal to or greater than in the least significant digit”
according to Semantic Versioning conventions.
In this case, Swift Package Manager will use the latest release
whose major is equal to 4 and minor release is equal 0
(that is, it will use 4.0.1 or4.0.0, but not 4.1.0 or 5.0.0).
For the PlayingCard module,
we don’t have to add an import specification for
because it’s already included as a dependency of DeckOfPlayingCards.
Finally,
the Cycle module is an external package
and includes an external import specification
that tells swift-sh how to add it as a dependency.
The notable difference here is that
the == operator is used to specify a revision
rather than a tagged release.
In an extension, conform Player to CustomStringConvertible
and implement the description property,
taking advantage of the convenient
Dictionary(grouping:by:)
initializer
added to Swift 4.
By convention,
a player’s hand is grouped by suit and ordered by rank.
In Bridge,
cards are dealt one-by-one to each player
until no cards remain.
We use the cycled() method
to rotate between each of our players
to ensure an even and fair deal.
On Unix systems,
a shebang (#!) indicates how a script should be interpreted.
In our case, the shebang line at the top of bridge.swift
tells the system to run the file using
the sh subcommand
of the swift command (/usr/bin/swift):
#!/usr/bin/swift sh
Doing so allows you to take the extra step
to make a Swift script look and act just like a binary executable.
Use mv to strip the .swift extension
and chmod to add executable (+x) permissions.
As a very early release,
it’s expected for there to be a few rough edges and missing features.
Here are some important details to keep in mind
as you’re getting started with swift-sh:
Imported dependencies can correspond to only GitHub repositories.
There’s currently no way to specify other remote or local locations.
We expect this to be added in a future release.
// 🔮 Possible Future SyntaximportRemote// git://example.com/Remote.gitimportLocal// ~/path/to/Local.git
swift-sh requires module names to match the name of their repository.
In many cases, this isn’t a problem
because projects typically have descriptive names.
However, in our example,
the DeckOfPlayingCards module
was provided by the repository
apple/example-package-deckofplayingcards.
You might expect the following syntax to be supported,
but this doesn’t work yet:
// 🔮 Possible Future SyntaximportDeckOfPlayingCards// @apple/example-package-deckofplayingcards
Until such an affordance is provided,
the easiest workaround is to fork the existing repository
and rename your fork
(as we did with @NSHipster/DeckOfPlayingCards).
As described in last week’s article,
Swift provides special syntax for
importing individual declarations from external modules.
In our example,
we import the Cycle package
to access its cycle() function,
which is used to iterate over the players during the initial deal repeatedly.
In a conventional Swift package setup,
we could import that function only.
However, that syntax isn’t yet supported by swift-sh.
// 🔮 Possible Future SyntaximportfuncCycle.cycle()// @NSHipster/Cycle
Until full support for import declaration syntax is added,
you’ll only be able to import external modules in their entirety.
Given the importance of this functionality,
we think swift-sh is destined to become part of the language.
As momentum and excitement build around this project,
keep an eye out in the Swift forums
for proposals to incorporate this as a language feature
in a future release.
The term “boilerplate” goes back to the early days of print media.
Small regional newspapers had column inches to fill,
but typically lacked the writing staff to make this happen,
so many of them turned to large print syndicates
for a steady flow of content that could be added verbatim
into the back pages of their dailies.
These stories would often be provided on pre-set plates,
which resembled the rolled sheets of steel used to make boilers,
hence the name.
Through a process of metonymy,
the content itself came to be known as “boilerplate”,
and the concept was appropriated to encompass standardized, formulaic text
in contracts, form letters, and,
most relevant to this week’s article on NSHipster, code.
Not all code can be glamorous.
In fact, a lot of the low-level infrastructure that makes everything work
is a slog of boilerplate.
This is true of the Swift standard library,
which includes families of types
like signed integers (Int8, Int16, Int32, Int64)
whose implementation varies only in the size of the respective type.
Copy-pasting code may work as a one-off solution
(assuming you manage to get it right the first time),
but it’s not sustainable.
Each time you want to make changes to these derived implementations,
you risk introducing slight inconsistencies
that cause the implementations to diverge over time —
not unlike the random mutations responsible for the variation of life on Earth.
Languages have various techniques to cope with this,
from C++ templates and Lisp macros to eval and C preprocessor statements.
Swift doesn’t have a macro system,
and because the standard library is itself written in Swift,
it can’t take advantage of C++ metaprogramming capabilities.
Instead, the Swift maintainers use a Python script called
gyb.py
to generate source code using a small set of template tags.
Later on,
in the implementation of SingleValueEncodingContainer,
these types are iterated over to generate the
methods declarations for the protocol’s requirements:
This pattern is used throughout the file
to generate similarly formulaic declarations for methods like
encode(_:forKey:), decode(_:forKey:), and decodeIfPresent(_:forKey:).
In total, GYB reduces the amount of boilerplate code by a few thousand LOC:
GYB isn’t part of the standard Xcode toolchain,
so you won’t find it with xcrun.
Instead, you can download it using Homebrew:
$ brew install nshipster/formulae/gyb
Alternatively, you can download the source code
and use the chmod command to make gyb executable
(the default installation of Python on macOS should be able to run gyb):
If you go this route,
be sure to move these somewhere that can be accessed from your Xcode project,
but keep them separate from your source files
(for example, a Vendor directory at your project root).
In Xcode, click on the blue project file icon in the navigator,
select the active target in your project,
and navigate to the “Build Phases” panel.
At the top, you’ll see a + symbol
that you can click to add a new build phase.
Select “Add New Run Script Phase”,
and enter the following into the source editor:
Now when you build your project
any file with the .swift.gyb file extension
is evaluated by GYB,
which outputs a .swift file
that’s compiled along with the rest of the code in the project.
As with any tool,
knowing when to use it is just as important as knowing how.
Here are some examples of when you might open your toolbox and reach for GYB.
Are you copy-pasting the same code for elements in a set
or items in a sequence?
A for-in loop with variable substitution might be the solution.
As seen in the example with Codable from before,
you can declare a collection at the top of your GYB template file
and then iterate over that collection
for type, property, or method declarations:
Just be aware that a lot of repetition is a code smell,
and may indicate that there’s a better way to accomplish your task.
Built-in language feature like protocol extensions and generics
can eliminate a lot of code duplication,
so be on the lookout to use these instead of brute-forcing with GYB.
If you want to see this in action,
check out
Currencies.swift.gyb
which generates Swift enumerations
for each of the currencies defined by the
ISO 4217 specification.
Code generation makes it trivial to keep your code in sync
with the relevant standards.
Simply update the data file and re-run GYB.
Swift has done a lot to cut down on boilerplate recently
with the addition of compiler synthesis of
Encodable and Decodable in 4.0,
Equatable and Hashable in 4.1, and
CaseIterable in 4.2.
We hope that this momentum is carried in future updates to the language.
In the meantime, for everything else,
GYB is a useful tool for code generation.
“Don’t Repeat Yourself” may be a virtue in programming,
but sometimes you have to say things a few times to make things work.
When you do, you’ll be thankful to have a tool like GYB to say it for you.
print is among the most-used functions in the Swift standard library.
Indeed, it’s the first function a programmer learns
when writing “Hello, world!”.
So it’s surprising how few of us are familiar with its other forms.
It’s like learning that your best friend “Chaz”
goes by his middle name
and that his full legal name is actually“R. Buckminster Charles Lagrand Jr.” —
oh, and also, they’ve had an identical twin the whole time.
Once you’ve taken a moment to collect yourself,
read on to find out the whole truth about a function
that you may have previously thought to need no further introduction.
Let’s start by taking a closer look at that function declaration from before:
So as an example,
let’s create a custom type that conforms to TextOutputStream.
Instead of writing a string to standard output verbatim,
we’ll have it inspect each constituent code point.
Conforming to the TextOutputStream protocol is simply a matter of
fulfilling the write(_:) method requirement.
In our implementation,
we iterate over each Unicode.Scalar value in the passed string;
the enumerated() collection method
provides the current offset on each loop.
At the top of the method,
a guard statement bails out early if the string is empty or a newline
(this reduces the amount of noise in the console).
To use our new UnicodeLogger type,
initialize it and assign it to a variable (with var)
so that it can be passed as an inout argument.
Anytime we want to get an X-ray of a string
instead of merely printing its surface representation,
we can tack on an additional parameter to our print statement.
Doing so allows us to reveal a secret about the emoji character 👨👩👧👧:
it’s actually a
sequence
of four individual emoji
joined by ZWJ characters —
seven code points in total!
Now that we know about an obscure part of the Swift standard library,
what can we do with it?
As it turns out,
there are plenty of potential use cases for TextOutputStream.
To get a better sense of what they are,
consider the following examples:
By default,
Swift print statements are directed to
standard output (stdout).
If you wanted to instead direct to
standard error (stderr),
you could create a new text output stream type
and use it in the following way:
The previous example of writing to stderr
can be generalized to write to any stream or file
by instead creating an output stream to a FileHandle
(for which standard error is accessible through a type property).
As a final example,
let’s imagine a situation in which you find yourself
frequently copy-pasting console output into a form on some website.
Unfortunately,
the website has the unhelpful behavior of
trying to parse < and > as if they were HTML.
Rather than taking an extra step to escape the text
each time you post to the site,
you could create a TextOutputStream
that takes care of that for you automatically
(in this case, we use an XML-escaping function
that we found buried deep in Core Foundation).
importFoundationstructXMLEscapingLogger:TextOutputStream{mutatingfuncwrite(_string:String){guard!string.isEmpty&&string!="\n",letxmlEscaped=CFXMLCreateStringByEscapingEntities(nil,stringasNSString,nil)else{return}print(xmlEscaped)}}varlogger=XMLEscapingLogger()print("<3",to:&logger)// Prints "<3"
Printing is a familiar and convenient way
for developers to understand the behavior of their code.
It complements more comprehensive techniques
like logging frameworks and debuggers,
and — in the case of Swift —
proves to be quite capable in its own right.
Have any other cool ideas for using TextOutputStream that you’d like to share?
Let us know on Twitter!
Swift is designed —
first and foremost —
to be a safe language.
Numbers and collections are checked for overflow,
variables are always initialized before first use,
optionals ensure that non-values are handle correctly,
and any potentially unsafe operations are named accordingly.
These language features go a long way to eliminate
some of the most common programming errors,
but we’d be remiss to let our guard down.
Today, I want to talk about one of the most exciting new features in Swift 5:
an overhaul to how values in string literals are interpolated
by way of the ExpressibleByStringInterpolation protocol.
A lot of folks are excited about the cool things you can do with it.
(And rightfully so! We’ll get to all of that in just a moment)
But I think it’s important to take a broader view of this feature
to understand the full scope of its impact.
Format strings are awful.
After incorrect NULL handling, buffer overflows, and uninitialized variables,
printf / scanf-style format strings
are arguably the most problematic holdovers from C-style programming language.
In the past 20 years,
security professionals have documented
hundreds of vulnerabilities
related to format string vulnerabilities.
It’s so commonplace that it’s assigned its very own
Common Weakness Enumeration
(CWE) category.
Not only are they insecure,
but they’re hard to use.
Yes, hard to use.
Consider the dateFormat property on DateFormatter,
which takes an strftime
format string.
If we wanted to create a string representation of a date
that included its 4-digit year,
we’d use "YYYY", as in "Y" for year
…right?
Huh what?
Turns out "YYYY" is the format for the
ISO week-numbering year,
which returns 2020 for December 31st, 2019
because the following day is a Wednesday
in the first week of the new year.
Format strings are the worst kind of hard to use,
because they’re so easy to use incorrectly.
And date format strings are the worst of the worst,
because it may not be clear that you’re doing it wrong until it’s too late.
They’re literal time bombs in your codebase.
The problem up until now has been that
APIs have had to choose between
dangerous-but-expressive domain-specific languages
(DSLs),
such as format strings,
and the correct-but-less-flexible method calls.
New in Swift 5,
the ExpressibleByStringInterpolation protocol
allows for these kinds of APIs to be both correct and expressive.
And in doing so,
it overturns decades’ worth of problematic behavior.
So without further ado,
let’s look at what ExpressibleByStringInterpolation is and how it works:
ExpressibleByStringInterpolation
Types that conform to the ExpressibleByStringInterpolation protocol
can customize how interpolated values
(that is, values escaped by \(...))
in string literals.
You can take advantage of this new protocol either by
extending the default String interpolation type
(DefaultStringInterpolation)
or by creating a new type that conforms to ExpressibleByStringInterpolation.
Extending Default String Interpolation
By default,
and prior to Swift 5,
all interpolated values in a string literal
were sent to directly to a String initializer.
Now with ExpressibleByStringInterpolation,
you can specify additional parameters
as if you were calling a method
(indeed, that’s what you’re doing under the hood).
As an example,
let’s revisit the previous mixup of "YYYY" and "yyyy"
and see how this confusion could be avoided
with ExpressibleByStringInterpolation.
By extending String’s default interpolationg type
(aptly-named DefaultStringInterpolation),
we can define a new method called appendingInterpolation.
The type of the first, unnamed parameter
determines which interpolation methods are available
for the value to be interpolated.
In our case,
we’ll define an appendInterpolation method that takes a Date argument
and an additional component parameter of type Calendar.Component
that we’ll use to specify which
importFoundation#if swift(<5)
#error("Download Xcode 10.2 Beta 2 to see this in action")#endif
extension DefaultStringInterpolation {
mutating func appendInterpolation(_ value: Date,
component: Calendar.Component)
{
let dateComponents =
Calendar.current.dateComponents([component],
from: value)
self.appendInterpolation(
dateComponents.value(for: component)!
)
}
}
Now we can interpolate the date for each of the individual components:
It’s verbose, yes.
But you’d never mistake .yearForWeekOfYear,
the calendar component equivalent of "YYYY",
for what you actually want.
But really,
we shouldn’t be formatting dates by hand like this anyway.
We should be delegating that responsibility to a DateFormatter:
You can overload interpolations just like any other Swift method,
and having multiple with the same name but different type signatures.
For example,
we can define interpolators for dates and numbers
that take a formatter of the corresponding type.
This allows for a consistent interface to equivalent functionality,
such as formatting interpolated dates and numbers.
letdateFormatter=DateFormatter()dateFormatter.dateStyle=.fulldateFormatter.timeStyle=.none"Today is \(Date(),formatter:dateFormatter)"// "Today is Monday, February 4, 2019"letnumberformatter=NumberFormatter()numberformatter.numberStyle=.spellOut"one plus one is \(1+1,formatter:numberformatter)"// "one plus one is two"
Implementing a Custom String Interpolation Type
In addition to extending DefaultStringInterpolation,
you can define custom string interpolation behavior on a custom type
that conforms to ExpressibleByStringInterpolation.
You might do that if any of the following is true:
You want to differentiate between literal and interpolated segments
You want to restrict which types can be interpolated
You want to support different interpolation behavior
than provided by default
You want to avoid burdening the built-in string interpolation type
with excessive API surface area
For a simple example of this,
consider a custom type that escapes values in XML,
similar to one of the loggers
that we described last week.
Our goal: to provide a nice templating API
that allows us to write XML / HTML
and interpolate values in a way that automatically escapes characters
like < and >.
We’ll start simply with a wrapper around a single String value.
We add conformance to ExpressibleByStringInterpolation in an extension,
just like any other protocol.
It inherits from ExpressibleByStringLiteral,
which requires an init(stringLiteral:) initializer.
ExpressibleByStringInterpolation itself requires
an init(stringInterpolation:) initializer
that takes an instance of the required, associated StringInterpolation type.
This associated StringInterpolation type is responsible for
collecting all of the literal segments
and interpolated values
from the string literal.
All literal segments are passed to the appendLiteral(_:) method.
For interpolated values,
the compiler finds the appendInterpolation method
that matches the specified parameters.
In this case,
both literal and interpolated values are collected into a mutable string.
With all of this in place,
we can now initialize XMLEscapedString with a string literal
that automatically escapes interpolated values.
(No XSS exploits for us, thank you!)
One of the best parts of this functionality
is how transparent its implementation is.
For behavior that feels quite magical,
you’ll never have to wonder how it works.
Compare the string literal above
to the equivalent API calls below:
Seeing how ExpressibleByStringInterpolation works,
it’s hard not to look around and find countless opportunities
for where it could be used:
Formatting
String interpolation offers a safer and easier-to-understand
alternative to date and number format strings.
Escaping
Whether its escaping entities in
URLs, XML documents, shell command arguments,
or values in SQL queries,
extensible string interpolation makes correct behavior seamless and automatic.
Decorating
Use string interpolation to create a type-safe DSL
for creating attributed strings for apps
and terminal output with ANSI control sequences for color and effects,
or pad unadorned text to match the desired alignment.
Localizating
Rather than relying on a a script that scans source code
looking for matches on “NSLocalizedString”,
string interpolation allows us to build tools that leverage the compiler
to find all instances of localized strings.
If you take all of these and factor in possible future support for
compile-time constant expression,
what you find is that Swift 5
may have just stumbled onto the new best way to deal with formatting.
Like everyone else in the Pacific Northwest,
we got snowed-in over the weekend.
To pass the time,
we decided to break out our stash of board games:
Carcassonne, Machi Koro, Power Grid, Pandemic;
we had plenty of excellent choices available.
But cooped up in our home for the afternoon,
we decided on a classic:
Cluedo.
Me, I’m an avid fan of Cluedo —
and yes, that’s what I’m going to call it.
Because despite being born and raised in the United States,
where the game is sold and marketed exclusively under the name “Clue”,
I insist on referring to it by its proper name.
(Otherwise, how would we refer to the 1985 film adaptation?)
Alas,
my relentless pedantry often causes me to miss out on invitations to play.
If someone were to ask:
varinvitation="Hey, would you like to play Clue?"invitation.contains("Cluedo")// false
…I’d have no idea what they were talking about.
If only they’d bothered to phrase it properly,
there’d be no question about their intention:
invitation="Fancy a game of Cluedo™?"invitation.contains("Cluedo")// true
Of course,
a regular expression
would allow me to relax my exacting standards.
I could listen for /Clue(do)?™?/
and never miss another invitation.
But who can be bothered to figure out regexes in Swift, anyway?
Well,
sharpen your pencils,
gather your detective notes,
and warm up your 6-sided dice,
because this week on NSHipster,
we crack the case of the cumbersome class known as NSRegularExpression.
Who killed regular expressions in Swift?
I have a suggestion:
It was NSRegularExpression, in the API, with the cumbersome usability.
In any other language,
regular expressions are something you can sling around in one-liners.
Need to substitute one word for another?Boom: regular expression.
Need to extract a value from a templated string?Boom: regular expression.
Need to parse XML?Boom: regular expressionactually, you should really use an XML parser in this case
But in Swift,
you have to go through the trouble of
initializing an NSRegularExpression object
and converting back and forth from String ranges to NSRange values.
It’s a total drag.
Here’s the good news:
You don’t need NSRegularExpression to use regular expressions in Swift.
Recent additions in Swift 4 and 5 make it much, much nicer
to use NSRegularExpression when you need to.
Let’s interrogate each of these points, in order:
Regular Expressions without NSRegularExpression
You may be surprised to learn that you can — in fact —
use regular expressions in a Swift one-liner:
you just have to bypass NSRegularExpression entirely.
Matching Strings Against Patterns
When you import the Foundation framework,
the Swift String type automatically gets access to
NSString instance methods and initializers.
Among these is range(of:options:range:locale:),
which finds and returns the first range of the specified string.
Normally,
this performs a by-the-books substring search operation.
Meh.
But, if you pass the .regularExpression option,
the string argument is matched as a pattern.
Eureka!
Let’s take advantage of this lesser-known feature
to dial our Cluedo sense to the “American” setting.
importFoundationletinvitation="Fancy a game of Cluedo™?"invitation.range(of:#"\bClue(do)?™?\b"#,options:.regularExpression)!=nil// true
If the pattern matches the specified string,
the method returns a Range<String.Index> object.
Therefore, checking for a non-nil value
tells us whether or not a match occurred.
The method itself provides default arguments to the
options, range, and locale parameters;
by default, it localized, unqualified search
over the entire string
according to the current locale.
Within a regular expression,
the ? operator matches the preceding character or group zero or one times.
We use it in our pattern
to make the “-do” in “Cluedo” optional
(accommodating both the American and correct spelling),
and allow a trademark symbol (™)
for anyone wishing to be prim and proper about it.
The \b metacharacters match if the current position is a word boundary,
which occurs between word (\w) and non-word (\W) characters.
Anchoring our pattern to match on word boundaries
prevents false positives like “Pseudo-Cluedo”.
That solves our problem of missing out on invitations.
The next question is how to respond in kind.
Searching and Retrieving Matches
Rather than merely checking for a non-nil value,
we can actually use the return value
to see the string that got matched.
importFoundationfuncrespond(toinvitation:String){ifletrange=invitation.range(of:#"\bClue(do)?™?\b"#,options:.regularExpression){switchinvitation[range]{case"Cluedo":
print("I'd be delighted to play!")case"Clue":
print("Did you mean Cluedo? If so, then yes!")default:fatalError("(Wait... did I mess up my regular expression?)")}}else{print("Still waiting for an invitation to play Cluedo.")}}
Conveniently,
the range returned by the range(of:...) method
can be plugged into a subscript to get a Substring
for the matching range.
Finding and Replacing Matches
Once we’ve established that the game is on,
the next step is to read the instructions.
(Despite its relative simplicity,
players often forget important rules in Cluedo,
such as needing to be in a room in order to suggest it.)
Naturally, we play the original, British edition.
But as a favor to the American players,
I’ll go to the trouble of localizing the rules on-the-fly.
For example, the victim’s name in the original version is “Dr. Black”,
but in America, it’s “Mr. Boddy”.
We automate this process
using the replacingOccurrences(of:with:options:) method —
again passing the .regularExpression option.
importFoundationletinstructions="""
The object is to solve by means of elimination and deduction
the problem of the mysterious murder of Dr. Black.
"""instructions.replacingOccurrences(of:#"(Dr.|Doctor) Black"#,with:"Mr. Boddy",options:.regularExpression)
Regular Expressions with NSRegularExpression
There are limits to what we can accomplish with
the range(of:options:range:locale:) and
replacingOccurrences(of:with:options:) methods.
Specifically,
you’ll need to use NSRegularExpression
if you want to match a pattern more than once in a string
or extract values from capture groups.
Enumerating Matches with Positional Capture Groups
A regular expression can match its pattern
one or more times on a string.
Within each match,
there may be one or more capture groups,
which are designated by enclosing by parentheses in the regex pattern.
For example,
let’s say you wanted to use regular expressions
to determine how many players you need to play Cluedo:
importFoundationletdescription="""
Cluedo is a game of skill for 2-6 players.
"""letpattern=#"(\d+)[ \p{Pd}](\d+) players"#letregex=tryNSRegularExpression(pattern:pattern,options:[])
This pattern includes two capture groups for
one or more digits,
as denoted by the + operator and \d metacharacter, respectively.
Between them, we match on a set containing a space
and any character in the
Unicode General CategoryPd (Punctuation, dash).
This allows us to match on
hyphen / minus (-), en dash (–), em dash (—),
or whatever other exotic typographical marks we might encounter.
We can use the enumerateMatches(in:options:range) method
to try each match until we find one that
has three ranges (the entire match and the two capture groups),
whose captured values can be used to initialize a valid range.
In the midst of all of this,
we use the new(-ish)
NSRange(_: in:) and Range(_:in:) initializers
to convert between String and NSString index ranges.
Once we find such a match,
we set the third closure parameter (a pointer to a Boolean value)
to true as a way to tell the enumeration to stop.
varplayerRange:ClosedRange<Int>?letnsrange=NSRange(description.startIndex..<description.endIndex,in:description)regex.enumerateMatches(in:description,options:[],range:nsrange){(match,_,stop)inguardletmatch=matchelse{return}ifmatch.numberOfRanges==3,letfirstCaptureRange=Range(match.range(at:1),in:description),letsecondCaptureRange=Range(match.range(at:2),in:description),letlowerBound=Int(description[firstCaptureRange]),letupperBound=Int(description[secondCaptureRange]),lowerBound>0&&lowerBound<upperBound{playerRange=lowerBound...upperBoundstop.pointee=true}}print(playerRange!)// Prints "2...6"
Each capture group can be accessed by position
by calling the range(at:) method on the match object.
*Sigh*.
What? No, we like the solution we came up with —
longwinded as it may be.
It’s just…
gosh, wouldn’t it be nice if we could play Cluedo solo?
Matching Multi-Line Patterns with Named Capture Groups
The only thing making Cluedo a strictly single-player affair
is that you need some way to test a theory
without revealing the answer to yourself.
If we wanted to write a program to check that
without spoiling anything for us,
one of the first steps would be to parse a suggestion
into its component parts:
suspect,
location, and
weapon.
letsuggestion="""
I suspect it was Professor Plum, \
in the Dining Room, \
with the Candlestick.
"""
When writing a complex regular expression,
it helps to know exactly which features your platform supports.
In the case of Swift,
NSRegularExpression is a wrapper around the
ICU regular expression engine,
which lets us do some really nice things:
First off,
declaring the pattern with a multi-line raw string literal
is a huge win in terms of readability.
That, in combination with the x and i flags within those groups,
allows us to use whitespace to organize our expression
into something more understandable.
Another nicety is how
this pattern uses named capture groups
(designated by (?<name>)) instead of the
standard, positional capture groups from the previous example.
Doing so allows us to access groups by name
by calling the range(withName:) method on the match object.
Beyond the more outlandish maneuvers,
we have affordances for regional variations, including
the spelling of “Miss Scarlet(t)”,
the title of “Mr. / Rev. Green”,
and the replacement of Mrs. White with Dr. Orchid
in standard editions after 2016.
Regular expressions are a powerful tool for working with text,
but it’s often a mystery how to get use them in Swift.
We hope this article has helped clue you into finding a solution.
When you import a module into Swift code,
you expect the result to be entirely additive.
That is to say:
the potential for new functionality comes at no expense
(other than, say, a modest increase in the size of your app bundle).
But it’d be surprising if, say,
the ability to distinguish between French and Japanese
interfered with your app’s ability to tell which way was magnetic north.
And though this particular example isn’t real
(to the relief of Francophones in Hokkaido),
there are situations in which a Swift dependency
can change how your app behaves —
even if you don’t use it directly.
In this week’s article,
we’ll look at a few ways that imported modules can
silently change the behavior of existing code,
and offer suggestions for how to
prevent this from happening as an API provider
and mitigate the effects of this as an API consumer.
Module Pollution
It’s a story as old as <time.h>:
two things are called Foo,
and the compiler has to decide what to do.
Pretty much every language with a mechanism for code reuse
has to deal with naming collisions one way or another.
In the case of Swift,
you can use fully-qualified names to distinguish
between the Foo type declared in module A (A.Foo)
from the Foo type in module B (B.Foo).
However, Swift has some unique characteristics
that cause other ambiguities to go unnoticed by the compiler,
which may result in a change to existing behavior
when modules are imported.
Operator Overloading
In Swift,
the + operator denotes concatenation
when its operands are arrays.
One array plus another results in
an array with the elements of the former array followed by those of the latter.
The Swift compiler is responsible for
resolving API calls to their corresponding implementations.
If an invocation matches more than one declaration,
the compiler selects the most specific one available.
To illustrate this point,
consider the following conditional extension on Array,
which defines the + operator to perform member-wise addition
for arrays whose elements conform to Numeric:
Because the requirement of Element: Numeric
is more specific than the unqualified declaration in the standard library,
the Swift compiler resolves + to this function instead.
Now,
these new semantics may be perfectly acceptable — indeed preferable.
But only if you’re aware of them.
The problem is that
if you so much as import a module containing such a declaration
you can change the behavior of your entire app without even knowing it.
This problem isn’t limited to matters of semantics;
it can also come about as a result of ergonomic affordances.
Function Shadowing
In Swift,
function declarations can specify default arguments for trailing parameters,
making them optional (though not necessarily Optional) for callers.
For example,
the top-level function
dump(_:name:indent:maxDepth:maxItems:)
has an intimidating number of parameters:
But thanks to default arguments,
you need only specify the first one to call it:
dump("🏭💨")// "🏭💨"
Alas,
this source of convenience can become a point of confusion
when method signatures overlap.
Imagine a hypothetical module that —
not being familiar with the built-in dump function —
defines a dump(_:) that prints the UTF-8 code units of a string.
The dump function declared in the Swift standard library
takes an unqualified generic T argument in its first parameter
(which is effectively Any).
Because String is a more specific type,
the Swift compiler will choose the imported dump(_:) method
when it’s available.
Unlike the previous example,
it’s not entirely clear that there’s any ambiguity
in the competing declarations.
After all,
what reason would a developer have to think that their dump(_:) method
could in any way be confused for dump(_:name:indent:maxDepth:maxItems:)?
Which leads us to our final example,
which is perhaps the most confusing at all…
String Interpolation Pollution
In Swift,
you can combine two strings by interpolation in a string literal
as an alternative to concatenation.
This has been true from the first release of Swift.
However, with the new
ExpressibleByStringInterpolation
protocol in Swift 5,
this behavior can no longer be taken for granted.
Consider the following extension on the default interpolation type for String:
Once again,
the Swift compiler’s notion of specificity
causes behavior to go from expected to unexpected.
If the previous declaration is made accessible by any module in your app,
it would change the behavior of all interpolated string values.
letgreeting="Hello, \(name)!"// "Hello, SWIFT!"
Given the rapid and upward trajectory of the language,
it’s not unreasonable to expect that these problems will be solved
at some point in the future.
But what are we to do in the meantime?
Here are some suggestions for managing this behavior
both as an API consumer and as an API provider.
Strategies for API Consumers
As an API consumer,
you are — in many ways —
beholden to the constraints imposed by imported dependencies.
It really shouldn’t be your problem to solve,
but at least there are some remedies available to you.
Add Hints to the Compiler
Often,
the most effective way to get the compiler to do what you want
is to explicitly cast an argument down to a type
that matches the method you want to call.
Take our example of the dump(_:) method from before:
by downcasting to CustomStringConvertible from String,
we can get the compiler to resolve the call
to use the standard library function instead.
If all else fails,
you can always solve the problem
by taking it into your own hands.
If you don’t like something that a third-party dependency is doing,
simply fork the source code,
get rid of the stuff you don’t want,
and use that instead.
(You could even try to get them to upstream the change.)
Strategies for API Provider
As someone developing an API,
it’s ultimately your responsibility to be deliberate and considerate
in your design decisions.
As you think about the greater consequences of your actions,
here are some things to keep in mind:
Be More Discerning with Generic Constraints
Unqualified <T> generic constraints are the same as Any.
If it makes sense to do so,
consider making your constraints more specific
to reduce the chance of overlap with unrelated declarations.
Isolate Core Functionality from Convenience
As a general rule,
code should be organized into modules
such that module is responsible for a single responsibility.
If it makes sense to do so,
consider packaging functionality provided by types and methods
in a separate module from
any extensions you provide to built-in types to improve their usability.
Until it’s possible to pick and choose which behavior we want from a module,
the best option is to give consumers the choice to opt-in to features
if there’s a chance that they might cause problems downstream.
Avoid Collisions Altogether
Of course,
it’d be great if you could knowingly avoid collisions to begin with…
but that gets into the whole
“unknown unknowns” thing,
and we don’t have time to get into epistemology now.
So for now,
let’s just say that if you’re aware of something maybe being a conflict,
a good option might be to avoid it altogether.
For example,
if you’re worried that someone might get huffy about
changing the semantics of fundamental arithmetic operators,
you could choose a different one instead, like .+:
As developers,
we’re perhaps less accustomed to considering the wider impact of our decisions.
Code is invisible and weightless,
so it’s easy to forget that it even exists
after we ship it.
But in Swift,
our decisions have impacts beyond what’s immediately understood
so it’s important to be considerate about how we exercise our responsibilities
as stewards of our APIs.
According to
this ranking of programming language popularity,
Swift and Objective-C are back to back around the 10th position —
a solid showing for both languages, all things considered.
…that is, unless you consider how they’re tied for 2nd and 3rd place
among the languages allowed on iOS, their most popular platform.
Whether you love it or hate it,
JavaScript has become the most important language for developers today.
Yet despite any efforts we may take to change or replace it
we’d be hard-pressed to deny its usefulness.
This week on NSHipster,
we’ll discuss the JavaScriptCore framework,
and how you can use it to set aside
your core beliefs in type safety and type sanity
and allow JavaScript to do some of the heavy lifting in your apps.
The JavaScriptCore framework provides
direct access to WebKit’s JavaScript engine in your apps.
You can execute JavaScript code within a context
by calling the evaluateScript(_:) method on a JSContext object.
evaluateScript(_:) returns a JSValue object
containing the value of the last expression that was evaluated.
For example,
a JavaScript expression that adds the numbers 1, 2, and 3
results in the number value 6.
You can cast JSValue to a native Swift or Objective-C type
by calling the corresponding method found in the following table:
A JSValue object
JavaScript Type
JSValue method
Objective-C Type
Swift Type
string
toString
NSString
String!
boolean
toBool
BOOL
Bool
number
toNumber toDouble toInt32 toUInt32
NSNumber double int32_t uint32_t
NSNumber! Double Int32 UInt32
Date
toDate
NSDate
Date?
Array
toArray
NSArray
[Any]!
Object
toDictionary
NSDictionary
[AnyHashable: Any]!
Class
toObject toObjectOfClass:
custom type
custom type
JavaScript evaluation isn’t limited to single statements.
When you evaluate code that declare a function or variable,
its saved into the context’s object space.
context.evaluateScript(#"""function triple(number) { return number * 3;}"""#)context.evaluateScript("triple(5)")?.toInt32()// 15
Handling Exceptions
The evaluateScript(_:) method
doesn’t expose an NSError ** pointer
and isn’t imported by Swift as a method that throws;
by default,
invalid scripts fail silently when evaluated within a context.
This is — you might say —
less than ideal.
To get notified when things break,
set the exceptionHandler property on JSContext objects
before evaluation.
importJavaScriptCoreletcontext=JSContext()!context.exceptionHandler={context,exceptioninprint(exception!.toString())}context.evaluateScript("**INVALID**")// Prints "SyntaxError: Unexpected token '**'"
Managing Multiple Virtual Machines and Contexts
Each JSContext executes on a JSVirtualMachine
that defines a shared object space.
You can execute multiple operations concurrently
across multiple virtual machines.
The default JSContext initializer
creates its virtual machine implicitly.
You can initialize multiple JSContext objects
to have a shared virtual machine.
A virtual machine performs deferred tasks,
such as garbage collection and WebAssembly compilation,
on the runloop on which it was initialized.
You can access named values from a JSContext
by calling the objectForKeyedSubscript(_:) method.
For example,
if you evaluate a script that declares the variable threeTimesFive
and sets it to the result of calling the triple() function
(declared previously),
you can access the resulting value by variable name.
In this example,
we initialize a Swift constant threeTimesTwo to the product of 2 and 3,
and set that value to a variable in context with the same name.
We can verify that the threeTimesTwo variable
is stored with the expected value
by performing an equality check in JavaScript.
Functions are different from other values in JavaScript.
And though you can’t convert a function contained in a JSValue
directly to a native function type,
you can execute it within the JavaScript context
using the call(withArguments:) method.
In this example,
we access the triple function from before by name
and call it —
passing 9 an argument —
to produce the value 27.
A similar limitation exists when you attempt to go the opposite direction,
from Swift to JavaScript:
JavaScriptCore is limited to passing Objective-C blocks
to JavaScript contexts.
In Swift,
you can use the @convention(block) to create a compatible closure.
In this example,
we define a block that multiplies an Int by 4 and returns the resulting Int,
and assign it to a function in the JavaScript context with the name quadruple.
We can verify this assignment by either
calling the function directly in evaluated JavaScript
or by using objectForKeyedSubscript(_:)
to get the function in a JSValue
and call it with the call(withArguments:) method.
Passing Swift Objects between Swift and JavaScript
All of the conversion between Swift and Javascript we’ve seen so far
has involved manual conversion with intermediary JSValue objects.
To improve interoperability between language contexts,
JavaScriptCore provides the JSExport protocol,
which allows native classes to be mapped and initialized directly.
…though to call the process “streamlined”
would be generous.
As we’ll see,
it takes quite a bit of setup to get this working in Swift,
and may not be worth the extra effort in most cases.
But for the sake of completeness,
let’s take a look at what all this entails:
Declaring the Exported JavaScript Interface
The first step is to declare a protocol that inherits JSExport.
This protocol defines the interface exported to JavaScript:
the methods that can be called; the properties that can be set and gotten.
For example,
here’s the interface that might be exported for a Person class
consisting of stored properties for firstName, lastName, and birthYear:
importFoundationimportJavaScriptCore// Protocol must be declared with `@objc`@objcprotocolPersonJSExports:JSExport{varfirstName:String{getset}varlastName:String{getset}varbirthYear:NSNumber?{getset}varfullName:String{get}// Imported as `Person.createWithFirstNameLastName(_:_:)`staticfunccreateWith(firstName:String,lastName:String)->Person}
JavaScriptCore uses the Objective-C runtime
to automatically convert values between the two languages,
hence the @objc attribute here and in the corresponding class declaration.
Conforming to the Exported JavaScript Interface
Next, create a Person class that adopts the PersonJSExports protocol
and makes itself Objective-C compatible with NSObject inheritance
and an @objc attribute for good measure.
// Class must inherit from `NSObject`@objcpublicclassPerson:NSObject,PersonJSExports{// Properties must be declared with `dynamic`dynamicvarfirstName:StringdynamicvarlastName:StringdynamicvarbirthYear:NSNumber?requiredinit(firstName:String,lastName:String){self.firstName=firstNameself.lastName=lastName}varfullName:String{return"\(firstName)\(lastName)"}classfunccreateWith(firstName:String,lastName:String)->Person{returnPerson(firstName:firstName,lastName:lastName)}}
Each stored property must be declared dynamic
to interoperate with the Objective-C runtime.
The init(firstName:lastName:) initializer won’t be accessible from JavaScript,
because it isn’t part of the exported interface declared by PersonJSExports;
instead, a Person object can be constructed through
a type method imported as Person.createWithFirstNameLastName(_:_:).
Registering the Class in the JavaScript Context
Finally,
register the class within the JSContext
by passing the type to setObject(_:forKeyedSubscript:).
With all of the setup out of the way,
we can now experience the singular beauty
of seamless(-ish) interoperability between Swift and JavaScript!
We’ll start by declaring a loadPeople() function in JavaScript,
which parses a JSON string and constructs imported Person objects
using the JSON attributes.
We can even flex our muscles by defining the JSON string in Swift
and then passing it as an argument to the loadPeople function
(accessed by name using the objectForKeyedSubscript(_:) method).
Going back and forth between languages like this is neat and all,
but doesn’t quite justify all of the effort it took to get to this point.
So let’s finish up with some NSHipster-brand pizazz,
and see decorate these aforementioned pioneers of computer science
with a touch of mustache.
Showing Off with Mustache
Mustache is a simple,
logic-less templating language,
with implementations in many languages,
including JavaScript.
We can load up mustache.js into our JavaScript context
using the evaluateScript(_:withSourceURL:)
to make it accessible for subsequent JS invocations.
From here,
we can define a mustache template (in all of its curly-braced glory)
using a Swift multi-line string literal.
This template —
along with the array of people from before in a keyed dictionary —
are passed as arguments to the render method
found in the Mustache object declared in context
after evaluating mustache.js.
lettemplate="""
{{#people}}
{{fullName}}, born {{birthYear}}
{{/people}}
"""letresult=context.objectForKeyedSubscript("Mustache").objectForKeyedSubscript("render").call(withArguments:[template,["people":people]])!print(result)// Prints:// "Grace Hopper, born 1906"// "Ada Lovelace, born 1815"// "Margaret Hamilton, born 1936"
The JavaScriptCore provides a convenient way to
leverage the entire JavaScript ecosystem.
Whether you use it to bootstrap new functionality,
foster feature parity across different platforms,
or extend functionality to users by way of a scripting interface,
there’s no reason not to consider what role JavaScript can play in your apps.
I just left a hipster coffee shop.
It was packed with iOS devs,
whispering amongst each other about how
they can’t wait for Apple to release
an official style guide and formatter for Swift.
Over the past few days,
the community has been buzzing about
the latest pitch from
Tony Allevato and
Dave Abrahams
to adopt an official style guide and formatting tool for the Swift language.
Dozens of community members have already weighed in on the draft proposal.
As with all matters of style,
opinions are strong, and everybody has one.
Fortunately,
the discourse from the community
has been generally constructive and insightful,
articulating a diversity of viewpoints, use cases, and concerns.
At the time of writing,
it appears that a plurality, if not an outright majority of respondents
are +1 (or more)
for some degree of official guidance on formatting conventions.
And those in favor of a sanctioned style guide
would also like for there to be a tool
that automatically diagnoses and fixes violations of these guidelines.
However, others have expressed concerns about
the extent to which these guidelines are applicable and configurable.
This week on NSHipster,
we’ll take a look at the current field of
Swift formatters available today —
including the swift-format tool released as part of the proposal —
and see how they all stack up.
From there,
we’ll take a step back and try to put everything in perspective.
But first,
let’s start with a question:
What is Code Formatting?
For our purposes,
we’ll define code formatting
as any change made to code that makes it easier to understand
without changing its behavior.
Although this definition extends to differences in equivalent forms,
(e.g. [Int] vs. Array<Int>),
we’ll limit our discussion here to whitespace and punctuation.
Swift, like many other programming languages,
is quite liberal in its acceptance of newlines, tabs, and spaces.
Most whitespace is insignificant,
having no effect on the code around from the compiler’s point of view.
When we use whitespace to make code more comprehensible
without changing its behavior,
that’s an example of
secondary notation;
the primary notation, of course,
being the code itself.
Put enough semicolons in the right places,
and you can write pretty much anything in a single line of code.
But all things being equal,
why not use horizontal and vertical whitespace
to visually structure code in a way that’s easier for us to understand,
right?
Unfortunately,
the ambiguity created by the compiler’s accepting nature of whitespace
can often cause confusion and disagreement among programmers:
“Should I add a newline before a curly bracket?
How do I break up statements that extend beyond the width of the editor?”
Organizations often codify guidelines for how to deal with these issues,
but they’re often under-specified, under-enforced, and out-of-date.
The role of a code formatter
is to automatically enforce a set of conventions
so that programmers can set aside their differences
and get to work solving actual problems.
Formatter Tool Comparison
The Swift community has considered questions of style from the very beginning.
Style guides have existed from the very first days of Swift,
as have various open source tools to automate the process
of formatting code to match them.
In order to get a sense of the current state of Swift code formatters,
we’ll take a look at the following four tools:
To establish a basis of comparison,
we’ll use the following code sample to evaluate each tool
(using their default configuration):
structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
Although code formatting encompasses a wide range of possible
syntactic and semantic transformations,
we’ll focus on newlines and indentation,
which we believe to be baseline requirements for any code formatter.
SwiftFormat
First up is
SwiftFormat,
a tool as helpful as it is self-descriptive.
You can install it by running the following command:
$ brew install swiftformat
In addition,
SwiftFormat also provides an Xcode Source Editor Extension,
found in the EditorExtension,
which you can use to reformat code in Xcode.
Or, if you’re a user of VSCode,
you can invoke SwiftFormat with
this plugin.
Usage
The swiftformat command formats each Swift file
found in the specified file and directory paths.
$ swiftformat Example.swift
SwiftFormat has a variety of rules that can be configured
either individually via command-line options
or using a configuration file.
Example
Running the swiftformat command on our example
using the default set of rules produces the following result:
// swiftformat version 0.39.4structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
As you can see,
this is a clear improvement over the original.
Each line is indented according to its scope,
and each declaration has consistent spacing between punctuation.
Both the semicolon in the property declarations
and the newline in the initializer parameters are preserved;
however, the closing curly braces aren’t moved to separate lines
as might be expected.
Performance
SwiftFormat is consistently the fastest of the tools tested in this article,
completing in a few milliseconds.
$time swiftformat Example.swift
0.03 real 0.01 user 0.01 sys
SwiftLint
Next up is,
SwiftLint,
a mainstay of the Swift open source community.
With over 100 built-in rules,
SwiftLint can to perform a wide variety of checks on your code —
everything from preferring AnyObject over class for class-only protocols
to the so-called “Yoda condition rule”,
which prescribes variables to be placed on
the left-hand side of comparison operators
(that is, if n == 42 not if 42 == n).
As its name implies,
SwiftLint is not primarily a code formatter;
it’s really a diagnostic tool for identifying
convention violation and API misuse.
However, by virtue of its auto-correction faculties,
it’s frequently used to format code.
Installation
You can install SwiftLint using Homebrew
with the following command:
Running the previous command on our example
yields the following:
// swiftlint version 0.31.0structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
SwiftLint cleans up the worst of the indentation and inter-spacing issues
but leaves other, extraneous whitespace intact.
Again, it’s worth noting that formatting isn’t SwiftLint’s primary calling;
if anything, it’s merely incidental to providing actionable code diagnostics.
And taken from the perspective of “first, do no harm”,
it’s hard to complain about the results here.
Time
For everything that SwiftLint checks for,
it’s remarkably snappy —
completing in a fraction of a second for our example.
$time swiftlint autocorrect --quiet--format--path Example.swift
0.11 real 0.05 user 0.02 sys
Prettier with Swift Plugin
If you’ve mostly shied away from JavaScript
(as discussed in last week’s article),
this may be the first you’ve heard of
Prettier.
On the other hand,
if you’re steeped in the world of ES6, React, and WebPack,
you’ve almost certainly come to rely on it.
Prettier is unique among code formatters in that it optimizes —
first and foremost —
for aesthetics,
wrapping lines of code onto newlines as if they were poetry.
To use Prettier and its plugin for Swift,
you’ll have to wade into the murky waters of the Node packaging ecosystem.
There are a few different approaches to get everything installed
(because of course there are),
but Yarn is our favorite 😻.
$ brew install yarn
$ yarn global add prettier prettier/plugin-swift
Usage
With the prettier command-line tool accessible from our $PATH,
run it with one or more file or directory paths.
$ prettier Example.swift
Example Output
Here’s the result of running the latest build of the Swift plugin with Prettier
on our example from before:
// prettier version 1.16.4// prettier/plugin-swift version 0.0.0 (bdf8726)structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:StringvarpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=region;self.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
Prettier describes itself to be “An opinionated code formatter”.
In practice, this means that there isn’t much in the way of configuration;
there are only two options: “regular code” and “prettier code”.
Now, you may object to the increase in vertical whitespace,
but you’d be lying if you said this code didn’t look amazing.
The way that everything is evenly spaced…
the way that long lines are wrapped and indented…
it’s almost hard to believe that you achieve something like this automatically.
Of course, our caveat from before still applies:
This is still very much a work-in-progress
and isn’t suitable for production use yet.
Also, there’s the matter of performance…
Timing
To put it bluntly:
Prettier is one or two orders of magnitude slower
than every other tool discussed in this article.
$time prettier Example.swift
1.14 real 0.56 user 0.38 sys
It’s unclear whether this is
a consequence of navigating a language barrier or
an opportunity for optimization,
but Prettier is slow enough to cause problems at scale.
For now,
we recommend using Prettier only for one-off formatting tasks,
such as writing code for articles and books.
swift-format
Having looked at the current landscape of available Swift formatters,
we now have a reasonable baseline for evaluating the swift-format tool
proposed by Tony Allevato and Dave Abrahams.
For your convenience,
we’re providing a Homebrew formula that builds from
our own fork of Google’s fork,
which you can install with the following command:
$ brew install nshipster/formulae/swift-format
Usage
Run the swift-format command,
passing one or more file and directory paths
to Swift files that you want to format.
$ swift-format Example.swift
The swift-format command also takes a --configuration option,
which takes a path to a JSON file.
For now,
the easiest way to customize swift-format behavior
is to dump the default configuration to a file
and go from there.
Using its default configuration,
here’s how swift-lint formats our example:
// swift-format version 0.0.1structShippingAddress:Codable{varrecipient:StringvarstreetAddress:Stringvarlocality:Stringvarregion:String;varpostalCode:Stringvarcountry:Stringinit(recipient:String,streetAddress:String,locality:String,region:String,postalCode:String,country:String){self.recipient=recipientself.streetAddress=streetAddressself.locality=localityself.region=regionself.postalCode=postalCodeguardcountry.count==2,country==country.uppercased()else{fatalError("invalid country code")}self.country=country}}letapplePark=ShippingAddress(recipient:"Apple, Inc.",streetAddress:"1 Apple Park Way",locality:"Cupertino",region:"CA",postalCode:"95014",country:"US")
For a version 0.0.1 release,
this is promising!
We could do without the original semicolon
and don’t much care for the colon placement for the region property, either,
but overall, this is pretty unobjectionable —
which is exactly what you’d want from an official code style tool.
Timing
In terms of performance,
swift-format is currently in the middle of the pack:
not so fast as to feel instantaneous,
but not so slow as to be an issue.
$time swift-format Example.swift
0.51 real 0.20 user 0.27 sys
Based on our initial investigation (albeit limited),
swift-format appears to offer a reasonable set of formatting conventions.
Going forward, it will be helpful to create more motivated examples
to help inform our collective beliefs about the contours
of such a tool.
No matter what,
it’ll be interesting to see how the proposal changes
and the discussion evolves around these issues.
Announced in 2018,
MapKit JS
takes the convenient cartographic capabilities of
the MapKit framework on iOS and macOS,
and brings them to the web.
MapKit JS —
along with MusicKit JS—
usher in a new generation of web APIs from Apple,
that’s defined by a brand new authentication flow based on
JSON Web Tokens (JWT).
These APIs also come at a time
when Swift on the Server is just starting to hit its stride,
making the browser-based offering all the more exciting.
This week on NSHipster,
we’ll zoom out and give turn-by-turn directions
of how to start your journey with MapKit JS.
(Once we figure out where we lost our keys)
on our way to a working integration.
Setting Up MapKit JS
To use MapKit JS on the web,
you’ll need an Apple Developer account.
Using that account,
you may obtain credentials for issuing tokens
that can be used to request map tiles, directions, and other mapping APIs.
You create and manage MapKit JS keys
from your Apple Developer Account Dashboard.
(The process should be familiar to anyone
who’s set up push notifications for their iOS app before.)
Step 1: Register a Maps ID
On the “Certificates, Identifiers & Profiles” page,
navigate to the Maps IDs section,
which is listed in the sidebar under the “Identifiers” heading.
Click the + button
and fill out the form,
providing a Maps ID description and
a unique, reverse-domain identifier
(such as maps.com.nshipster).
Step 2: Create a New MapKit JS Key
Next,
go to the all keys page
found in the sidebar under the “Keys” heading.
Again, click the + button
and proceed to fill out the form,
selecting the checkbox that enable MapKit JS key services
and configuring it to use the Maps ID created in the previous step.
Step 3: Download the Key
Finally,
go back to the all keys page,
click on the key you just created,
and click the Download button.
At this point,
you should now have a file named something like AuthKey_Key ID.p8
sitting in your Downloads folder.
“But what is a .p8 file?”, you might ask.
Well, the truth is…
P8, PEM, ASN.1
.p8 is a made-up file extension;
the AuthKey_Key ID.p8 file you downloaded
is a text file like any other.
If you open it up,
you can see that it is, indeed, text:
The convention of delimiting text-encoded binary data
with -----BEGIN THING----- and -----END THING-----
is known as PEM format,
which is named after
Privacy-Enhanced Mail.
The text sandwiched between those delimiters
is the Base64-encoding of the private key,
represented in ASN.1.
At this point,
you may feel like you took a wrong turn
and drove right into an ocean of acronyms.
This wouldn’t be surprising,
as drowning in alphabet soup
is an occupational hazard of the software development profession —
especially when it comes to matters cryptographic.
But worry not!
You can make it back to shore just by treading water.
All you need to know at this point
is that you’ll use the private key in the .p8 file
to let Apple know that our requests for map data are coming from us.
JSON Web Tokens
JSON Web Tokens (JWT)
are a way for claims to be securely communicated between two parties
They’re an open standard,
codified by RFC 7519.
A JSON Web Token has three distinct parts
consisting of Base64-encoded segments
joined by a period (.).
The first segment represents the header,
which distinguishes the token type,
and information needed to verify the identity of the signer.
The second segment is the payload,
which contains one of more claims
to be transmitted between two parties.
The third segment is the signature,
which can be cryptographically verified using the contents of the message
and one or more keys.
JSON web tokens can be daunting when seen up close,
but the structure makes more sense when you see it visually:
Apple Maps JS requires claims for
the issuer (iss),
the date of issuing (iat),
and the expiration date (exp);
you may optionally specify an origin
to restrict which hosts are allowed to make requests.
To verify that claims are made by who they claim to be,
tokens are signed and encrypted using the
ES256 algorithm.
Signing MapKit JS Tokens
To sign a JWT token for MapKit JS,
we’ll need the following 3 pieces of information:
A private key provided by Apple
The ID of the private key
The ID of the team that owns the private key
Once you have all of that,
it’s simply a matter of creating a JWT Header),
creating a JWT claims object,
and signing it using the ES256 algorithm.
Although you could generate a single API key that doesn’t expire,
that opens your account to abuse
from those who might take advantage of these generous terms.
Here, a “principle of least privilege” is generally preferred;
instead of issuing one immortal token,
issue tokens that expire after a short period of time,
and require clients to request new ones as necessary.
Of course,
in order for this to work,
we need to keep the private key secret from the client.
To do that, we’ll store it on a server
and mediate access by way of responses to token requests.
Serving Tokens with a Web Application
Here’s a simple web application using the Vapor framework.
When a GET request is made to the host root (/),
a static HTML page is served that displays a map.
The code that generates the map, in turn,
sends a GET request to the /token endpoint
to get a JWT in plaintext
that it can use to initialize MapKit JS.
Below, the implementation of the generateSignedJWTToken() function
is essentially what we had in the previous code listing.
Before we can request map tiles,
we need to have Apple’s servers give us the OK.
We do this by calling the init method,
which takes an authorizationCallback;
the function takes a single closure parameter,
which is called asynchronously when the token is received
by the fetch request.
Alternatively,
if what you’re making is unlikely to leak outside of your localhost,
you could certainly take a short cut and hard-code a long-lived token.
Instead of waiting for a fetch to finish,
you simply call the done function straight away:
Now that we’ve gone through all of that trouble
to get map tiles hooked up for our website,
let’s do a quick drive-by of the actual MapKit API:
MapKit JS should be familiar to anyone familiar with
the original MapKit framework on iOS and macOS.
For example,
when you initialize a map,
you configure its initial view
by either constructing a region from
a fixed distance around center point.
Annotations work much the same,
except with arguably nicer affordances out of the box.
For example,
mapkit.MarkerAnnotation offers the same, familiar pushpin shape
that we iOS developers have always wanted —
with simple, power knobs for customization.
constannotation=newmapkit.MarkerAnnotation(center,{title:"Apple Park Visitor Center",subtitle:"10600 North Tantau Avenue, Cupertino, CA 95014",glyphText:"",color:"#8e8e93",displayPriority:1000});map.addAnnotation(annotation);
With only a few lines of JavaScript and HTML,
we can embed a beautiful little map into our webpages
using MapKit JS.
MapKit JS joins a robust ecosystem of mapping providers.
Currently in beta,
it offers
250,000 map initializations and
25,000 service requests
for free per day (!),
which is quite generous —
especially when compared to similar offerings
from Google
and MapBox.
So if you’ve been kicking around an idea for
a map widget to add to your site
or a directions feature for your web app,
you might want to give MapKit JS a look!
Swift 2 introduced error handling by way of the
throws, do, try and catch keywords.
It was designed to work hand-in-hand with
Cocoa error handling conventions,
such that any type conforming to the ErrorProtocol protocol
(since renamed to Error)
was implicitly bridged to NSError and
Objective-C methods with an NSError** parameter,
were imported by Swift as throwing methods.
For the most part,
these changes offered a dramatic improvement over the status quo
(namely, no error handling conventions in Swift at all).
However, there were still a few gaps to fill
to make Swift errors fully interoperable with Objective-C types.
as described by Swift Evolution proposal
SE-0112: “Improved NSError Bridging”.
Not long after these refinements landed in Swift 3,
the practice of declaring errors in enumerations
had become idiomatic.
Yet for how familiar we’ve all become with
Error (née ErrorProtocol),
surprisingly few of us are on a first-name basis with
the other error protocols to come out of SE-0112.
Like, when was the last time you came across LocalizedError in the wild?
How about RecoverableError?
CustomNSErrorqu’est-ce que c’est?
At the risk of sounding cliché,
you might say that these protocols are indeed pretty obscure,
and there’s a good chance you haven’t heard of them:
LocalizedError
A specialized error that provides
localized messages describing the error and why it occurred.
RecoverableError
A specialized error that may be recoverable
by presenting several potential recovery options to the user.
CustomNSError
A specialized error that provides a
domain, error code, and user-info dictionary.
If you haven’t heard of any of these until now,
you may be wondering when when you’d ever use them.
Well, as the adage goes,
“There’s no time like the present”.
This week on NSHipster,
we’ll take a quick look at each of these Swift Foundation error protocols
and demonstrate how they can make your code —
if not less error-prone —
than more enjoyable in its folly.
Communicating Errors to the User
Too many cooks spoil the broth.
Consider the following Broth type
with a nested Error enumeration
and an initializer that takes a number of cooks
and throws an error if that number is inadvisably large:
structBroth{enumError{case tooManyCooks(Int)
}init(numberOfCooks:Int)throws{precondition(numberOfCooks>0)guardnumberOfCooks<redactedelse{throwError.tooManyCooks(numberOfCooks)}// ... proceed to make broth}}
If an iOS app were to communicate an error
resulting from broth spoiled by multitudinous cooks,
it might do so
with by presenting a UIAlertController
in a catch statement like this:
importUIKitclassViewController:UIViewController{overridefuncviewDidAppear(_animated:Bool){super.viewDidAppear(animated)do{self.broth=tryBroth(numberOfCooks:100)}catchleterrorasBroth.Error{lettitle:Stringletmessage:Stringswitcherror{case .tooManyCooks(let numberOfCooks):
title="Too Many Cooks (\(numberOfCooks))"message="""
It's difficult to reconcile many opinions.
Reduce the number of decision makers.
"""}letalertController=UIAlertController(title:title,message:message,preferredStyle:.alert)alertController.addAction(UIAlertAction(title:"OK",style:.default))self.present(alertController,animated:true,completion:nil)}catch{// handle other errors...}}}
Such an implementation, however,
is at odds with well-understood boundaries between models and controllers.
Not only does it create bloat in the controller,
and it doesn’t scale to handling multiple errors
or handling errors in multiple contexts.
To reconcile these anti-patterns,
let’s turn to our first Swift Foundation error protocol.
Adopting the LocalizedError Protocol
The LocalizedError protocol inherits the base Error protocol
and adds four instance property requirements.
These properties map 1:1 with familiar NSErroruserInfo keys.
Requirement
User Info Key
errorDescription
NSLocalizedDescriptionKey
failureReason
NSLocalizedFailureReasonErrorKey
recoverySuggestion
NSLocalizedRecoverySuggestionErrorKey
helpAnchor
NSHelpAnchorErrorKey
Let’s take another pass at our nested Broth.Error type
and see how we might refactor error communication from the controller
to instead be concerns of LocalizedError conformance.
importFoundationextensionBroth.Error:LocalizedError{varerrorDescription:String?{switchself{case .tooManyCooks(let numberOfCooks):
return"Too Many Cooks (\(numberOfCooks))"}}varfailureReason:String?{switchself{case .tooManyCooks:
return"It's difficult to reconcile many opinions."}}varrecoverySuggestion:String?{switchself{case .tooManyCooks:
return"Reduce the number of decision makers."}}}
Using switch statements may be overkill
for a single-case enumeration such as this,
but it demonstrates a pattern that can be extended
for more complex error types.
Note also how pattern matching is used
to bind the numberOfCooks constant to the associated value
only when it’s necessary.
Now we can
importUIKitclassViewController:UIViewController{overridefuncviewDidAppear(_animated:Bool){super.viewDidAppear(animated)do{trymakeBroth(numberOfCooks:100)}catchleterrorasLocalizedError{lettitle=error.errorDescriptionletmessage=[error.failureReason,error.recoverySuggestion].compactMap{$0}.joined(separator:"\n\n")letalertController=UIAlertController(title:title,message:message,preferredStyle:.alert)alertController.addAction(UIAlertAction(title:"OK",style:.default))self.present(alertController,animated:true,completion:nil)}catch{// handle other errors...}}}
If that seems like a lot of work just to communicate an error to the user…
you might be onto something.
Although UIKit borrowed many great conventions and idioms from AppKit,
error handling wasn’t one of them.
By taking a closer look at what was lost in translation,
we’ll finally have the necessary context to understand
the two remaining error protocols to be discussed.
Communicating Errors on macOS
If at first you don’t succeed, try, try again.
Communicating errors to users is significantly easier on macOS than on iOS.
For example,
you might construct and pass an NSError object
to the presentError(_:) method,
called on an NSWindow.
importAppKit@NSApplicationMainclassAppDelegate:NSObject,NSApplicationDelegate{@IBOutletweakvarwindow:NSWindow!funcapplicationDidFinishLaunching(_aNotification:Notification){do{_=trysomething()}catch{window.presentError(error)}}funcsomething()throws->Never{letuserInfo:[String:Any]=[NSLocalizedDescriptionKey:NSLocalizedString("The operation couldn’t be completed.",comment:"localizedErrorDescription"),NSLocalizedRecoverySuggestionErrorKey:NSLocalizedString("If at first you don't succeed...",comment:"localizedErrorRecoverSuggestion")]throwNSError(domain:"com.nshipster.error",code:1,userInfo:userInfo)}}
Doing so presents a modal alert dialog
that fits right in with the rest of the system.
But macOS error handling isn’t merely a matter of convenient APIs;
it also has built-in mechanisms for allowing
users to select one of several options
to attempt to resolve the reported issue.
Recovering from Errors
To turn a conventional NSError into
one that supports recovery,
you specify values for the userInfo keys
NSRecoveryAttempterErrorKey and NSRecoveryAttempterErrorKey.
A great way to do that
is to override the application(_:willPresentError:) delegate method
and intercept and modify an error before it’s presented to the user.
extensionAppDelegate{funcapplication(_application:NSApplication,willPresentErrorerror:Error)->Error{varuserInfo:[String:Any]=(errorasNSError).userInfouserInfo[NSLocalizedRecoveryOptionsErrorKey]=[NSLocalizedString("Try, try again",comment:"tryAgain")NSLocalizedString("Give up too easily",comment:"giveUp")]userInfo[NSRecoveryAttempterErrorKey]=selfreturnNSError(domain:(errorasNSError).domain,code:(errorasNSError).code,userInfo:userInfo)}}
For NSLocalizedRecoveryOptionsErrorKey,
specify an array of one or more localized strings
for each recovery option available the user.
For NSRecoveryAttempterErrorKey,
set an object that implements the
attemptRecovery(fromError:optionIndex:) method.
With just a few lines of code,
you’re able to facilitate a remarkably complex interaction,
whereby a user is alerted to an error and prompted to resolve it
according to a set of available options.
Cool as that is,
it carries some pretty gross baggage.
First,
the attemptRecovery requirement is part of an
informal protocol,
which is effectively a handshake agreement
that things will work as advertised.
Second,
the use of option indexes instead of actual objects
makes for code that’s as fragile as it is cumbersome to write.
Fortunately,
we can significantly improve on this
by taking advantage of Swift’s superior type system
and (at long last) the second subject of this article.
Modernizing Error Recovery with RecoverableError
The RecoverableError protocol,
like LocalizedError is a refinement on the base Error protocol
with the following requirements:
The recoveryOptions property requirement
is equivalent to the NSLocalizedRecoveryOptionsErrorKey:
an array of strings that describe the available options.
The attemptRecovery functions
formalize the previously informal delegate protocol;
func attemptRecovery(optionIndex:)
is for “application” granularity,
whereas
attemptRecovery(optionIndex:resultHandler:)
is for “document” granularity.
Supplementing RecoverableError with Additional Types
On its own,
the RecoverableError protocol improves only slightly on
the traditional, NSError-based methodology
by formalizing the requirements for recovery.
Rather than implementing conforming types individually,
we can generalize the functionality
with some clever use of generics.
First,
define an ErrorRecoveryDelegate protocol
that re-casts the attemptRecovery methods from before
to use an associated, RecoveryOption type.
Requiring that RecoveryOption conforms to CaseIterable,
allows us to vend options directly to API consumers
independently of their presentation to the user.
From here,
we can define a generic DelegatingRecoverableError type
that wraps an Error type
and associates it with the aforementioned Delegate,
which is responsible for providing recovery options
and attempting recovery with the one selected.
Now we can refactor the previous example of our macOS app
to have AppDelegate conform to ErrorRecoveryDelegate
and define a nested RecoveryOption enumeration
with all of the options we wish to support.
extensionAppDelegate:ErrorRecoveryDelegate{enumRecoveryOption:String,CaseIterable,CustomStringConvertible{case tryAgain
case giveUp
vardescription:String{switchself{case .tryAgain:
returnNSLocalizedString("Try, try again",comment:self.rawValue)case .giveUp:
returnNSLocalizedString("Give up too easily",comment:self.rawValue)}}}funcattemptRecovery(fromerror:Error,withoption:RecoveryOption)->Bool{do{ifoption==.tryAgain{trysomething()}}catch{window.presentError(error)}returntrue}funcapplication(_application:NSApplication,willPresentErrorerror:Error)->Error{returnDelegatingRecoverableError(recoveringFrom:error,with:self)}}
The result?
…wait, that’s not right.
What’s missing?
To find out,
let’s look at our third and final protocol in our discussion.
Improving Interoperability with Cocoa Error Handling System
The CustomNSError protocol
is like an inverted NSError:
it allows a type conforming to Error
to act like it was instead an NSError subclass.
The protocol requirements correspond to the
domain, code, and userInfo properties of an NSError, respectively.
Now, back to our modal from before:
normally, the title is taken from userInfo via NSLocalizedDescriptionKey.
Types conforming to LocalizedError can provide this too
through their equivalent errorDescription property.
And while we could extend DelegatingRecoverableError
to adopt LocalizedError,
it’s actually much less work to add conformance for CustomNSError:
With this one additional step,
we can now enjoy the fruits of our burden.
In programming,
it’s often not what you know,
but what you know about.
Now that you’re aware of the existence of
LocalizedError, RecoverableError, CustomNSError,
you’ll be sure to identify situations in which they might
improve error handling in your app.
Useful AF, amiright?
Then again,
“Familiarity breeds contempt”;
so often,
what initially endears one to ourselves
is what ultimately causes us to revile it.
Back then,
Xcode was a supposedly impenetrable castle that we’d leaned a couple of ladders against.
Like a surprisingly considerate horde,
we scaled the walls and got to work on some much-needed upkeep.
Those were heady days of
in-process code injection, an informally sanctioned and thriving ecosystem of third-party plugins —
all backed up by an in-app package manager.
For a while, Apple tolerated it all.
But with the introduction of System Integrity Protection in 2016,
the ladders were abruptly kicked away.
(Pour one out for Alcatraz why don’t we,
with a chaser for XcodeColors.
Miss you buddy.)
Plugins allowed us to tweak pretty much everything about Xcode:
window layout, syntactic and semantic highlighting, changing UI elements,
boilerplate generation, project analysis, bindings for something called Vim (?).
Looking back at NSHipster’s favorites, some are now thankfully included as a standard feature:
inserting documentation comments,
switch statement autocompletion
or — astonishingly — line breaks in the issue navigator.
Most of the inventive functionality that plugins added, though, has just plain gone.
Xcode 8 proposed a solution for the missing plugins in the form of Source Editor Extensions.
Like other macOS extensions, they can be sold via the App Store or distributed independently.
But some bad, if old, news:
unlike plugins, these new extensions are seriously limited in scope.
They allow
pure text manipulation, instigated by the user from a menu command, on one source file at a time—
none of the fun stuff, in other words.
Source Editor Extensions have remained unchanged since introduction.
We’ll discuss signs that might point to interesting future developments.
But if IDEs with an open attitude are more your thing,
there’s not much to see here yet.
Let’s start, though, by looking at the official situation today:
Source Editor Extensions
By now, Apple platform developers will be familiar with extension architecture:
separate binaries, sandboxed and running in their own process,
but not distributable without a containing app.
Compared to using a tool like Homebrew, installation is undoubtedly a pain:
After finding, downloading and launching the containing app,
the extension shows up in the Extensions pane of System Preferences.
You can then activate it,
restart Xcode
and it should manifest itself as a menu item.
(App Store reviewers love this process.)
That’s the finished result.
To understand how you get to that point,
let’s create a simple extension of our own.
This sample project
transforms TODO, FIXME and MARK code marks to be uppercased with a trailing colon,
so Xcode can recognize them and add them to the quick navigation bar.
(It’s just one of the rules more fully implemented by the SwiftFormat extension.)
Creating a Source Editor Extension
Create a new Cocoa app as the containing app,
and add a new target using the Xcode Source Editor Extension template.
The target contains ready-made XCSourceEditorExtension and XCSourceEditorCommand subclasses,
with a configured property list.
Both of those superclasses are part of the XcodeKit framework
(hence the XC prefix),
which provides extensions the ability to modify the text and selections of a source file.
Display Names
User-facing strings for an extension are sprinkled around the extension’s Info.plist or defined at runtime:
Display text
Property
Definition
Extension name, as shown in System Preferences
Bundle Display Name
Info.plist
Top level menu item for extension
Bundle Name
Info.plist
Individual menu command
XCSourceEditorCommandName
Info.plist
XCSourceEditorCommandDefinitionKey.nameKey
Runtime
Menu Items
The only way a user can interact with an extension is by selecting one of its menu items.
These show up at the bottom of the Editor menu when viewing a source code file.
Xcode’s one affordance to users is that keybindings can be assigned to extension commands,
just as for other menu items.
Each command gets a stringly-typed identifier, display text, and a class to handle it,
which are each defined in the extension target’s Info.plist.
Alternatively,
we can override these at runtime
by providing a commandDefinitions property on the XCSourceEditorExtension subclass.
The commands can all be funneled to a single XCSourceEditorCommand subclass
or split up to be handled by multiple classes — whichever you prefer.
In our extension, we just define a single “Format Marks” menu item:
varcommandDefinitions:[[XCSourceEditorCommandDefinitionKey:Any]]{letnamespace=Bundle(for:type(of:self)).bundleIdentifier!letmarker=MarkerCommand.className()return[[.identifierKey:namespace+marker,.classNameKey:marker,.nameKey:NSLocalizedString("Format Marks",comment:"format marks menu item")]]}
When the user chooses one of the menu commands defined by the extension,
the handling class is called with
perform(with:completionHandler:).
The extension finally gets access to something useful,
namely the contents of the current source code file.
Inputs and Outputs
The passed XCSourceEditorCommandInvocation argument holds a reference to the XCSourceTextBuffer,
which gives us access to:
completeBuffer, containing the entire text of the file as a single String
another view on the same text, separated into lines of code
an array of current selections in terms of lines and columns, supporting multiple cursors
various indentation settings
the type of source file
With text and selections in hand, we get to do the meaty work of the extension.
Then XcodeKit provides two ways to write back to the same source file,
by mutating either the completeBuffer or the more performant lines property.
Mutating one changes the other,
and Xcode applies those changes once the completion handler is called.
Modifying the selections property updates the user’s selection in the same way.
In our example,
we first loop over the lines of code.
For each line,
we use a regular expression
to determine if it has a code mark that needs reformatting.
If so, we note the index number and the replacement line.
Finally we mutate the lines property to update the source file,
and call the completion handler to signal that we’re done:
funcperform(withinvocation:XCSourceEditorCommandInvocation,completionHandler:@escaping(Error?)->Void)->Void{replaceLines(in:invocation.buffer.lines,by:formattingMarks)completionHandler(nil)}funcreplaceLines(inlines:NSMutableArray,byreplacing:@escaping(String)->String?){guardletstrings=linesas?[String]else{return}letnewStrings:[(Int,String)]=strings.enumerated().compactMap{let(index,line)=$0guardletreplacementLine=replacing(line)else{returnnil}return(index,replacementLine)}newStrings.forEach{let(index,newString)=$0lines[index]=newString}}funcformattingMarks(instring:String)->String?{/* Regex magic transforms:
"// fixme here be 🐉"
to
"// FIXME: here be 🐉"
*/}
Development Tips
Debugging
Debugging the extension target launches it in a separate Xcode instance, with a dark status bar and icon:
Sometimes attaching to the debugger fails silently,
and it’s a good idea
to set a log or audible breakpoint to track this:
Two suggestions from Daniel Jalkut to make life easier.
Firstly add Xcode as the default executable in the Extension scheme’s Run/Info pane:
Secondly, add a path to a file or project containing some good code to test against,
in the Run/Arguments panel of the extension’s scheme, under Arguments Passed On Launch:
Testing XcodeKit
Make sure the test target knows how to find the XcodeKit framework,
if you need to write tests against it.
Add ${DEVELOPER_FRAMEWORKS_DIR} as both a Runpath and a Framework Search Path in Build Settings:
Using pluginkit
During development, Xcode can become confused as to which extensions it sees.
It can be useful to get an overview of installed extensions using the pluginkit tool.
This allows us to query the private PluginKit framework that manages all system extensions.
Here we’re matching by the NSExtensionPointIdentifier for Source Editor extensions:
If you spot an extension that might be causing an issue, you can try manually removing it:
$ pluginkit -r path/to/extension
Finally, when multiple copies of Xcode are on the same machine, extensions can stop working completely.
In this case, Apple Developer Relations suggests re-registering your main copy of Xcode with Launch Services
(it’s easiest to temporarily add lsregister’s location to PATH first):
Given how limited XcodeKit’s text API is, what sorts of things are people making?
And can it entice tool creators away from the command line?
(Hint: 😬)
All the tools mentioned above are clearly transforming source code in various ways.
They’ll need some information about the structure of that code to do useful work.
Could they be using SourceKit directly?
Well, where the extension is on the App Store, we know that they’re not.
The extension must be sandboxed just to be loaded by Xcode,
whereas calls to SourceKit needs to be un-sandboxed,
which of course won’t fly in the App Store.
We could distribute independently and use an un-sandboxed XPC service embedded in the extension.
Or more likely, we can write our own single-purpose code to get the job done.
The power of Xcode’s compiler is tantalizingly out of reach here.
An opportunity, though, if writing a mini-parser sounds like fun
(🙋🏼,
and check out SwiftFormat’s beautiful lexer implementation for Swift).
Context-free Source Code
Once we have some way to analyze source code,
how sophisticated an extension can we then write?
Let’s remember that the current API gives us access to a file of text,
but not any of its context within a project.
As an example,
say we want to implement an extension that quickly modifies the access level of Swift code to make it part of a framework’s API.
So an internal class’s internal properties and functions get changed to public,
but private or fileprivate implementation details are left alone.
We can get most of the way there,
lexing and parsing the file to figure out where to make appropriate changes,
taking into account Swift’s rules about access inheritance.
But what happens if one of these transformed methods turns out to have a parameter with an internal type?
If that type is declared in a different file, there’s no way for our extension to know,
and making the method public will cause a build error:
“Method cannot be declared public because its parameter uses an internal type”.
In this example, we’re missing type declarations in other files.
But complex refactorings can need information about how an entire codebase fits together.
Metadata could also be useful,
for example,
what version of Swift the project uses, or a file path to save per-project configuration.
This is a frustrating trade-off for safety.
While it’s feasible to transform the purely syntactic parts of isolated code,
once any semantics come into play we quickly bump up against that missing context.
Output
You can only output transformed text back to the same source file using the extension API.
If you were hoping to generate extensive boilerplate and insert project files automatically,
this isn’t supported and would be fragile to manage via the containing app.
Anonymous source file in/out sure is secure, but it isn’t powerful.
Heavyweight Architecture; Lightweight Results
Most extensions’ containing apps are hollow shells
with installation instructions and some global preferences.
Why?
Well, a Cocoa app can do anything,
but the extension doesn’t give us a lot to work with:
As creators, we must deal with
sandboxed communications to the containing app, the limited API and entitlements.
Add complete sandboxing when distributing through the App Store.
As users we contend with
that convoluted installation experience,
and managing preferences for each extension separately in the containing apps.
It’s all, effectively, for the privilege of a menu item.
And the upshot is apparent from a prominent example in the Mac App Store,
Swiftify:
they suggest no fewer than four superior ways to access their service,
over using their own native extension.
The Handwavy Bit
To further belabor the Xcode-as-castle metaphor,
Apple has opened the gate just very slightly,
but also positioned a large gentleman behind it,
deterring all but the most innocuous of guests.
Extensions might have temporarily pacified the horde,
but they are no panacea.
After nearly three years without expanding the API,
it’s no wonder that the App Store is not our destination of choice to augment Xcode.
And Apple’s “best software for most” credo doesn’t mean they always get the IDE experience right
cough image literals autocompletion cough,
or make us optimistic that Xcode will become truly extensible in the style of VSCode.
But let’s swirl some tea leaves and see where Apple could take us
if they so wished:
Imagine a world where Xcode is using SwiftSyntax directly to represent the syntax of a file
(a stated goal of the project).
Let’s imagine that XcodeKit exposes Syntax nodes in some way through the extension API.
We would be working with exactly the same representation as Xcode —
no hand-written parsers needed.
Tools are already being written against this library —
it would be so neat to get them directly in Xcode.
Let’s imagine we have specific read/write access to the current project directory and metadata.
Perhaps this leverages the robust entitlements system, with approval through App Review.
That sounds good to create extensive boilerplate.
Let’s expand our vision:
there’s a way to access fuller semantic information about our code,
maybe driven via the LSP protocol.
Given a better way to output changes too,
we could use that information for complex, custom refactorings.
Imagine invoking extensions automatically,
for example as part of the build.
Imagine API calls that add custom UI or Touch Bar items, according to context.
Imagine a thriving, vibrant section of the Mac App Store for developer extensions.
Whew.
That magic tea is strong stuff.
In that world, extensions look a lot more
fun, powerful, and worth the architectural hassles.
Of course,
this is rank speculation,
and yet…
The open-source projects Apple is committed to working on will — eventually —
change the internal architecture of Xcode,
and surely stranger things are happening.
For now, though, if any of this potential excites you,
please write or tweet about it,
submit enhancement requests,
get involved on the
relevantforums
or contribute directly.
We’re still hoping the Xcode team renders this article comprehensively obsolete,
sooner rather than later 🤞.
Since the progress of civilization in our country
has furnished thousands of convenient places for this Swallow to breed in,
safe from storms, snakes, or quadrupeds,
it has abandoned, with a judgment worthy of remark,
its former abodes in the hollows of trees,
and taken possession of the chimneys which emit no smoke in the summer season.
For this reason, no doubt,
it has obtained the name by which it is generally known.
John J Audubon, Birds of America, Plate 158: “American Swift”
Vaux’s swifts (Chaetura vauxi) is a species of swift
native to the American Pacific Northwest and South America.
Like others in its genus,
vaux’s swifts are impressive aerialists,
capable of high-precision maneuvers at speeds in excess of 100 km/h.
On the other hand, they are frequently described as
“small, dark, fast-flying cigars with wings”,
which isn’t a particularly majestic characterization.
In the Alphabet District of Portland Oregon
(a short walk from NSHipster headquarters, as it were),
Chapman Elementary School
is host to North America’s largest concentration of Vaux’s swifts.
Every evening, from late summer through October,
thousands of swifts can be seen just before sunset
as they fly into the school’s old smokestack to roost for the night.
At dawn, they emerge once again
and continue their migration to Central and South America.
Vaux’s are among the more gregarious species of swifts,
observed to flock in the dozens.
Moving together as a group,
the whirling mass of birds flying in and out of their roost
creates a phantasmal silhouette against the twilight sky .
Among the first computer simulations of this flocking behavior
was a program called Boids,
created by Craig Reynolds in 1986.
It remains one of the most striking examples of emergent behavior,
with complex — seemingly chaotic — interactions
arising from a small set of simple rules:
separation:
steer to avoid crowding local flockmates
alignment:
steer towards the average heading of local flockmates
cohesion:
steer to move towards the average position
(center of mass) of local flockmates
The following simulation
is an implementation of Craig Reynold’s “Boids” program,
created by Daniel Shiffman using Processing.js.
(Click or tap to add a new bird.)
As you gaze upon this computational approximation of flocking swifts,
consider, for a moment, the emergent nature of your own behavior.
What separates you from others?
How often is your direction more a consequence of your surroundings
than a reasoned, conscious choice?
And when you are, indeed, making such a decision,
how is your choice shaped by the consensus of your peers?
…or don’t.
Such philosophical introspection is but a fool’s errand.
The term “boilerplate” goes back to the early days of print media.
Small regional newspapers had column inches to fill,
but typically lacked the writing staff to make this happen,
so many of them turned to large print syndicates
for a steady flow of content that could be added verbatim
into the back pages of their dailies.
These stories would often be provided on pre-set plates,
which resembled the rolled sheets of steel used to make boilers,
hence the name.
Through a process of metonymy,
the content itself came to be known as “boilerplate”,
and the concept was appropriated to encompass standardized, formulaic text
in contracts, form letters, and,
most relevant to this week’s article on NSHipster, code.
Not all code can be glamorous.
In fact, a lot of the low-level infrastructure that makes everything work
is a slog of boilerplate.
This is true of the Swift standard library,
which includes families of types
like signed integers (Int8, Int16, Int32, Int64)
whose implementation varies only in the size of the respective type.
Copy-pasting code may work as a one-off solution
(assuming you manage to get it right the first time),
but it’s not sustainable.
Each time you want to make changes to these derived implementations,
you risk introducing slight inconsistencies
that cause the implementations to diverge over time —
not unlike the random mutations responsible for the variation of life on Earth.
Languages have various techniques to cope with this,
from C++ templates and Lisp macros to eval and C preprocessor statements.
Swift doesn’t have a macro system,
and because the standard library is itself written in Swift,
it can’t take advantage of C++ metaprogramming capabilities.
Instead, the Swift maintainers use a Python script called
gyb.py
to generate source code using a small set of template tags.
How GYB Works
GYB is a lightweight templating system
that allows you to use Python code
for variable substitution and flow control:
The sequence %{ code } evaluates a block of Python code
The sequence % code: ... % end manages control flow
The sequence ${ code } substitutes the result of an expression
All other text is passed through unchanged.
A good example of GYB can be found in Codable.swift.gyb.
At the top of the file,
the base Codable types are assigned to an instance variable:
Later on,
in the implementation of SingleValueEncodingContainer,
these types are iterated over to generate the
methods declarations for the protocol’s requirements:
This pattern is used throughout the file
to generate similarly formulaic declarations for methods like
encode(_:forKey:), decode(_:forKey:), and decodeIfPresent(_:forKey:).
In total, GYB reduces the amount of boilerplate code by a few thousand LOC:
GYB isn’t part of the standard Xcode toolchain,
so you won’t find it with xcrun.
Instead, you can download it using Homebrew:
$ brew install nshipster/formulae/gyb
Alternatively, you can download the source code
and use the chmod command to make gyb executable
(the default installation of Python on macOS should be able to run gyb):
If you go this route,
be sure to move these somewhere that can be accessed from your Xcode project,
but keep them separate from your source files
(for example, a Vendor directory at your project root).
Using GYB in Xcode
In Xcode, click on the blue project file icon in the navigator,
select the active target in your project,
and navigate to the “Build Phases” panel.
At the top, you’ll see a + symbol
that you can click to add a new build phase.
Select “Add New Run Script Phase”,
and enter the following into the source editor:
Now when you build your project
any file with the .swift.gyb file extension
is evaluated by GYB,
which outputs a .swift file
that’s compiled along with the rest of the code in the project.
When to Use GYB
As with any tool,
knowing when to use it is just as important as knowing how.
Here are some examples of when you might open your toolbox and reach for GYB.
Generating Formulaic Code
Are you copy-pasting the same code for elements in a set
or items in a sequence?
A for-in loop with variable substitution might be the solution.
As seen in the example with Codable from before,
you can declare a collection at the top of your GYB template file
and then iterate over that collection
for type, property, or method declarations:
A lot of repetition in code is a smell
and may indicate that there’s a better way to accomplish your task.
Built-in language feature like protocol extensions and generics
can eliminate a lot of code duplication,
so be on the lookout to use these instead of brute-forcing with GYB.
Generating Code Derived from Data
Are you writing code based on a data source?
Try incorporating GYB into your development!
GYB files can import Python packages like json, xml, and csv,
so you can parse pretty much any kind of file you might encounter:
If you want to see this in action,
check out
Currencies.swift.gyb
which generates Swift enumerations
for each of the currencies defined by the
ISO 4217 specification.
Code generation makes it trivial to keep your code in sync
with the relevant standards.
Simply update the data file and re-run GYB.
Swift has done a lot to cut down on boilerplate recently
with the addition of compiler synthesis of
Encodable and Decodable in 4.0,
Equatable and Hashable in 4.1, and
CaseIterable in 4.2.
We hope that this momentum is carried in future updates to the language.
In the meantime, for everything else,
GYB is a useful tool for code generation.
“Don’t Repeat Yourself” may be a virtue in programming,
but sometimes you have to say things a few times to make things work.
When you do, you’ll be thankful to have a tool like GYB to say it for you.
UITableView is the bread and butter of iOS apps.
This is as true today as it was with the first iPhone over a decade ago.
Back in those early days,
developers worked hard to achieve smooth scroll performance —
often resorting to extreme measures.
For example,
to achieve 60FPS on a table view with custom cells on an iPhone 3G
you’d often have to draw text directly to a cell’s Core Graphics context,
because compositing subviews was too slow.
(Interface Builder? Auto Layout?
Phooey!
Back in my day, we calculated all of our view frames by hand —
up hill, both ways, in the snow)
At the time,
the highest praise a developer could receive for their efforts
was to have someone describe their as “buttery”:
smooth, responsive, without any jitter.
And we milked that hardware for all it was worth to make that happen.
In honor of all the skeuomorphic sweat spilled
to transmute code into that most golden of dairy products,
and in the interest of maximizing the performance of our apps today,
we’ll turn our attention to a class that —
for many of us —
has been hiding in plain sight:
UITableViewHeaderFooterView.
Introduced in iOS 6,
UITableViewHeaderFooterView
takes the reuse functionality of table view cells
and makes it available to section headers and footers.
You can either use it directly
or create a subclass to customize its appearance.
Now,
table views are responsible for a great deal of functionality,
and one can easily lose track of how all of its responsibilities are delegated.
So let’s start with a quick run-down of UITableView
before going into more detail about UITableViewHeaderFooterView:
UITableView Review
A UITableView consists of sections,
each of which containing a number of rows.
For each row,
the table view’s dataSource is responsible for returning a UITableViewCell
to represent each section / row index path
with the tableView(_:cellForRowAt:) delegate method.
The table view’s dataSource may also provide
a title to be displayed in the header or footer of a section
by implementing the optional
tableView(_:titleForHeaderInSection:) and
tableView(_:titleForFooterInSection:) delegate methods.
To customize the appearance of section headers or footers,
the table view’s delegate can implement the optional delegate methods
tableView(_:viewForHeaderInSection:) and
tableView(_:viewForFooterInSection:).
To keep scroll performance snappy,
table views recycle their cells as they scroll out of view.
This process is known as cell reuse.
You can take advantage of reuse for section headers and footers,
by returning an instance of UITableViewHeaderFooterView (or a subclass).
What better way to demonstrate this obscure technique for buttery performance
create an app to display per-capita dairy product consumption statistics
from the USDA?
(That was a hypothetical question.)
For our example,
we’ll keep our model nice and simple,
with a nearly 1:1 mapping with the API we’ll use to
display this information on a table view:
Alright, let’s cut the cheese
and talk about the right wheyway
to use UITableViewHeaderFooterView.
Creating a Section Header View
In this example,
we’ll offer two different approaches to
working with UITableViewHeaderFooterView.
In the first,
we’ll do everything in code;
in the second,
we’ll design things visually in Interface Builder.
Feel free to adopt whichever one you prefer.
Option 1: Constructing the View Programmatically
Similar to UITableViewCell
each UITableViewHeaderFooterView comes with
textLabel and detailTextLabel properties
that are lazily created and positioned within a contentView.
As with cells,
you have the option to take or leave these built-in subviews
for your custom subclass.
For our example,
let’s use the existing textLabel
and add an imageView along the trailing margin of the contentView.
We do all of this in the designated initializer,
init(reuseIdentifier:):
In our view controller,
we register the custom section header in viewDidLoad()
by calling the register(_:forHeaderFooterViewReuseIdentifier:) method
on tableView:
Dynamic table view cells can be designed directly from a Storyboard,
which can be quite convenient for prototyping interfaces.
Unfortunately, at the time of writing,
there is no documented way to design prototype section header / footer views
as you can with table view cells.
However,
we can still use Interface Builder to design our section header and footer views —
all it takes a few extra steps.
First, create a new Swift file
that declares your UITableViewHeaderFooterView subclass.
Next,
create a new XIB file for your custom view:
In Interface Builder,
navigate to the Identity Inspector
in the Inspectors panel on the right-hand side,
and set your subclass as the “Custom Class” for both
File’s Owner and the top-level view.
Back in your subclass implementation,
declare an imageView property
and an override to the existing textLabel property —
both with @IBOutlet annotations —
and connect them to their counterparts in Interface Builder.
importUIKitfinalclassSectionHeaderView:UITableViewHeaderFooterView{staticletreuseIdentifier:String=String(describing:self)staticvarnib:UINib{returnUINib(nibName:String(describing:self),bundle:nil)}// Override `textLabel` to add `@IBOutlet` annotation@IBOutletoverridevartextLabel:UILabel?{get{return_textLabel}set{_textLabel=newValue}}privatevar_textLabel:UILabel?@IBOutletvarimageView:UIImageView!}
Now, when you register your subclass for reuse with the table view controller,
pass a UINib (provided here in a type property)
instead of SectionHeaderView.self.
With today’s comparatively over-powered iOS hardware,
such proactive measures may well be unnecessary for achieving
buttery smooth interactions.
But for those of your with demanding performance requirements,
for anyone yearning to be in the 2%,
to achieve the crème de la crème of responsive interfaces,
UITableViewHeaderFooterView can be a great way to skim some fat off your code.
If nothing else,
its restrained, familiar API allows UITableViewHeaderFooterView
to be added to most codebases without introducing much churn.
Accessibility features on iOS are more like superpowers
than assistive technologies.
Open the Settings app and navigate to General > Accessibility
and you’ll find a treasure trove of functionality,
capable of feats otherwise impossible on a platform as locked down as iOS.
Want to use your iPhone as a magnifying glass?
Turn on Magnifier for a convenient way to
zoom in on small details with your camera.
Care to learn the contents of a webpage
without as much as glancing at your screen?
Enable “Speak Screen” or “Speak Selection”
to have the page dictated, not read.
Disagree with the aesthetic direction of iOS
since Jony Ive took over UI design in version 7?
With just a few taps,
you can do away with frivolous transparency and motion effects
and give buttons the visual distinction they deserve.
But venture down to the bottom of the accessibility settings menu,
and you’ll find arguably the most obscure among these accessibility features:
Guided Access.
What is it?
Why is it useful?
How can you build your app to better support it?
Read on,
and let NSHipster be your guide.
What Is Guided Access?
Guided Access
is an accessibility feature introduced in iOS 6
that restricts user interactions within an app.
When a Guided Access session is started,
the user is unable to close the app until the session is ended
(either by entering a passcode or authenticating with Face ID or Touch ID).
Additionally,
a Guided Access session can be configured
to block interactions with designated screen regions
and allow or deny any combination of the following features:
Sleep / Wake Button:
Prevent the screen and device from being turned off
Volume Buttons:
Disable hardware volume buttons
Motion:
Ignore device rotation and shake gestures
Keyboards
Don’t show the keyboard
Touch
Ignore screen touches
Time Limit
Enforce a given time limit for using the app
Why is Guided Access Useful?
With a name like “Guided Access”,
it’s not immediately clear what this feature actually does.
And its section heading “Learning” doesn’t help much, either —
though, to be fair, that isn’t an inaccurate characterization
(Guided Access is undoubtedly useful for people with a learning disability),
but it certainly buries the lede.
In truth, Guided Access can be many things to many different people.
So for your consideration,
here are some alternative names that you can keep at the back of your mind
to better appreciate when and why you might give it a try:
“Kid-Proof Mode”: Sharing Devices with Children
If you have a toddler and want to facilitate a FaceTime call with a relative,
start a Guided Access session before you pass the device off.
This will prevent your little one from accidentally hanging up
or putting the call on hold by switching to a different app.
“Adult-Proof Mode”: Sharing Devices with Other Adults
The next time you go to hand off your phone to someone else to take a photo,
give it a quick triple-tap to enter Guided Access mode first
to forego the whole “Oops, I accidentally locked the device” routine.
“Crowd-Proof Mode”: Displaying a Device in Kiosk Mode
Have a spare iPad that you want to allows guests to sign-in at an event?
Guided Access offers a quick and effective way to keep things moving.
“You-Proof Mode”: Focus Your Attention on a Device
Guided Access can be helpful even when
you aren’t handing off the device to someone else:
If you’re prone to distraction and need to focus on study or work,
Guided Access can help keep you on track.
Conversely,
if you’re kicking back and enjoying a game on your phone,
but find the touch controls to be frustratingly similar to built-in iOS gestures,
you can use Guided Access to keep you safely within the “magic circle”.
(The same goes for anyone whose work looks more like play,
such as digital musicians and other performers.)
Setting Up Guided Access
To set up Guided Access,
open the Settings app,
navigate to General > Accessibility > Guided Access,
and enable the switch labeled Guided Access.
Next, tap Passcode Settings,
and enter (and reenter) the passcode
that you’ll use to end Guided Access sessions.
Starting a Guided Access Session
To start a Guided Access session,
triple-click the home button
(or, on the iPhone X and similar models, the side button).
Alternatively, you can start a session by telling Siri
“Turn on Guided Access”.
From here,
you can trace regions of the screen
for which user interaction is disabled,
and configure which of the aforementioned features are allowed.
As far as accessibility features are concerned,
Guided Access is the least demanding for developers:
Nearly all are compatible as-is, without modification.
However,
there are ways that you might improve —
and even enhance —
Guided Access functionality in your app:
Detecting When Guided Access is Enabled
To determine whether the app is running within a Guided Access session,
access the isGuidedAccessEnabled type property
from the UIAccessibility namespace:
UIAccessibility.isGuidedAccessEnabled
You can use NotificationCenter to subscribe to notifications
whenever a Guided Access session is started or ended
by observing for UIAccessibility.guidedAccessStatusDidChangeNotification.
NotificationCenter.default.addObserver(forName:UIAccessibility.guidedAccessStatusDidChangeNotification,object:nil,queue:.main){(notification)inrespond to notification}
All of that said:
most apps won’t really be an actionable response
to Guided Access sessions starting or ending —
at least not unless they extend this functionality
by adding custom restrictions.
Adding Custom Guided Access Restrictions to Your App
If your app performs any destructive (i.e. not easily reversible) actions
that aren’t otherwise precluded by any built-in Guided Access restrictions,
you might consider providing a custom restriction.
To get a sense of what these might entail,
think back to the previous use-cases for Guided Access and consider:
Which functionality would I might not want to expose to a toddler / stranger / crowd?
Some ideas that quickly come to mind are
deleting a photo from Camera Roll,
overwriting game save data,
or anything involving a financial transaction.
A custom restriction can be enabled or disabled as part of a Guided Access session
like any of the built-in restrictions.
However, unlike the ones that come standard in iOS,
it’s the responsibility of your app to determine
how a restriction behaves.
Let’s take a look at what that means in code:
Defining Custom Guided Access Restrictions
First,
create an enumeration with a case
for each restriction that you want your app to support.
Each restriction needs a unique identifier,
so it’s convenient to make that the rawValue.
Conformance to CaseIterable in the declaration here
automatically synthesizes an allCases type property that we’ll use later on.
For this example,
let’s define a restriction that, when enabled,
prevents a user from initiating a purchase within a Guided Access session:
Adopting the UIGuidedAccessRestrictionDelegate Protocol
Once you’ve defined your custom restrictions,
extend your AppDelegate to adopt
the UIGuidedAccessRestrictionDelegate protocol,
and have it conform by implementing the following methods:
For guidedAccessRestrictionIdentifiers,
we can simply return a mapping of the rawValue for each of the cases.
For textForGuidedAccessRestriction(withIdentifier:),
one convenient pattern is to leverage optional chaining
on a computed text property.
The last protocol method to implement is
guidedAccessRestriction(withIdentifier:didChange:),
which notifies our app when access restrictions are turned on and off.
funcguidedAccessRestriction(withIdentifierrestrictionIdentifier:String,didChangenewRestrictionState:UIAccessibility.GuidedAccessRestrictionState){letnotification:NotificationswitchnewRestrictionState{case .allow:
notification=Notification(name:UIAccessibility.guidedAccessDidAllowRestrictionNotification,object:restrictionIdentifier)case .deny:
notification=Notification(name:UIAccessibility.guidedAccessDidDenyRestrictionNotification,object:restrictionIdentifier)@unknowndefault:// Switch covers known cases,// but 'UIAccessibility.GuidedAccessRestrictionState'// may have additional unknown values,// possibly added in future versionsreturn}NotificationCenter.default.post(notification)}
Really, though, most of the responsibility falls on each view controller
in determining how to respond to this kind of change.
So here, we’ll rebroadcast the message as a notification.
The existing UIAccessibility.guidedAccessStatusDidChangeNotification
fires when Guided Access is switched on or off,
but it’s unclear from the documentation what the contract is for
changing options in a Guided Access session without entirely ending it.
So to be safe,
we’ll define additional notifications that we can use to respond accordingly:
Responding to Changes in Custom Guided Access Restrictions
Finally,
in our view controllers,
we’ll register for all of the guided access notifications we’re interested in,
and define a convenience method to respond to them.
For example,
ProductViewController has a puchaseButton outlet
that’s configured according to the custom .purchase restriction
defined by the app:
Accessibility is an important issue for developers —
especially for mobile and web developers,
who are responsible for designing and implementing
the digital interfaces on which we increasingly depend.
Each of us,
(if we’re fortunate to live so long),
are almost certain to have our ability to
see or hear diminish over time.
“Accessibility is designing for our future selves”,
as the popular saying goes.
But perhaps it’d be more accurate to say
“Accessibility is designing for our future selvesour day-to-day selves“.
Even if you don’t identify as someone who’s differently-abled,
there are frequently situations in which you might be temporarily impaired,
whether it’s trying to
read in low-light setting
or listen to someone in a loud environment
or interact with a device while holding a squirming toddler.
Features like Guided Access offer a profound reminder
that accessibility features benefit everyone.