Quantcast
Channel: NSHipster
Viewing all 382 articles
Browse latest View live

CharacterSet

$
0
0

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 PropertyUnicode General Categories & Code Points
alphanumericsL*, M*, N*
lettersL*, M*
capitalizedLetters*Lt
lowercaseLettersLl
uppercaseLettersLu, Lt
nonBaseCharactersM*
decimalDigitsNd
punctuationCharactersP*
symbolsS*
whitespacesZs, U+0009
newlinesU+000A – U+000D, U+0085, U+2028, U+2029
whitespacesAndNewlinesZ*, U+000A – U+000D, U+0085
controlCharactersCc, Cf
illegalCharactersCn

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.

"""😴
        """.trimmingCharacters(in:.whitespacesAndNewlines)// "😴"

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:

letquery="character set"query.addingPercentEncoding(withAllowedCharacters:.urlQueryAllowed)// "character%20set"

Building Your Own

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.

varallowed=CharacterSet()allowed.formUnion(.lowercaseLetters)allowed.formUnion(.uppercaseLetters)allowed.formUnion(.decimalDigits)allowed.insert(charactersIn:"!@#$%&")funcvalidate(_input:String)->Bool{returninput.unicodeScalars.allSatisfy{allowed.contains($0)}}

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:

letdisallowed=allowed.invertedfuncvalidate(_input:String)->Bool{returninput.rangeOfCharacter(from:disallowed)!=nil}

Caching Character Sets

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.

extensionCharacterSet{staticvaremoji:CharacterSet{letbase64Encoded="""
        AAAAAAgE/wMAAAAAAAAAAAAAAAAA...
        """letdata=Data(base64Encoded:base64Encoded)!returnCharacterSet(bitmapRepresentation:data)}}CharacterSet.emoji.contains("👺")// true

Bundles and Packages

$
0
0

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

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.

Accessing Bundle Contents

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:

Bundle.main.url(forResource:"Photo",withExtension:"jpg")

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:

URLPathDescription
executableURLexecutablePathThe executable
url(forAuxiliaryExecutable:)path(forAuxiliaryExecutable:)The auxiliary executables
resourceURLresourcePathThe subdirectory containing resources
sharedFrameworksURLsharedFrameworksPathThe subdirectory containing shared frameworks
privateFrameworksURLprivateFrameworksPathThe subdirectory containing private frameworks
builtInPlugInsURLbuiltInPlugInsPathThe subdirectory containing plug-ins
sharedSupportURLsharedSupportPathThe subdirectory containing shared support files
appStoreReceiptURL The App Store receipt

Getting App Information

All app bundles are required to have an Info.plist file that contains information about the app.

Some metadata is accessible directly through instance properties on bundles, including bundleURL and bundleIdentifier.

importFoundationletbundle=Bundle.mainbundle.bundleURL// "/path/to/Example.app"bundle.bundleIdentifier// "com.nshipster.example"

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).

bundle.infoDictionary["CFBundleName"]// "Example"bundle.localizedInfoDictionary["CFBundleName"]// "Esempio" (`it_IT` locale)

Getting Localized Strings

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.

importFoundationletbundle=Bundle.mainbundle.localizedString(forKey:"Hello, %@",value:"Hello, ${username}",table:nil)

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.

NSLocalizedString("Hello, %@",comment:"Hello, ${username}")
$ find .\(-name"*.swift"!\ # find all Swift files            ! -path "./Carthage/*"      \ # ignoring dependencies
                    ! -path "./Pods/*"          \ # from Carthage and CocoaPods
                 \)    |                        \
        tr '\n' '\0' |                        \ # change delimiter to NUL
          xargs -0 genstrings -o .              \ # to handle paths with spaces
        

Packages

Packages are primarily for improving user experience by encapsulating and consolidating related resources into a single unit.

A directory is considered to be a package by the Finder if any of the following criteria are met:

  • The directory has a special extension like .app, .playground, or .plugin
  • The directory has an extension that an app has registered as a document type
  • The directory has an extended attribute designating it as a package *

Accessing the Contents of a Package

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.

Determining if a Directory is a Package

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(_:_:):

importFoundationimportCoreServicesfuncdirectoryIsPackage(_url:URL)->Bool{letfilenameExtension:CFString=url.pathExtensionasNSStringguardletuti=UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,filenameExtension,nil)?.takeRetainedValue()else{returnfalse}returnUTTypeConformsTo(uti,kUTTypePackage)}letxcode=URL(fileURLWithPath:"/Applications/Xcode.app")directoryIsPackage(xcode)// true

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.

Equatable and Comparable

$
0
0

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.


Equatable

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:

structBinomen{letgenus:Stringletspecies:String}let🐺=Binomen(genus:"Canis",species:"lupis")let🐻=Binomen(genus:"Ursus",species:"arctos")

We can add Equatable conformance through an extension, implementing the required type method for the == operator like so:

extensionBinomen:Equatable{staticfunc==(lhs:Binomen,rhs:Binomen)->Bool{returnlhs.genus==rhs.genus&&lhs.species==rhs.species}}🐺==🐺// true🐺==🐻// false

Easy enough, right?

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:

structBinomen:Equatable{letgenus:Stringletspecies:String}🐺==🐺// true🐺==🐻// false

The Benefits of Being Equal

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.

[🐺,🐻].contains(🐻)// truefunccommonName(forbinomen:Binomen)->String?{switchbinomen{case🐺:return"gray wolf"case🐻:return"brown bear"default:returnnil}}commonName(for:🐺)// "gray wolf"

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 Limits of Automatic Synthesis

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.

Conditional Conformance to Equality

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:

letoneHen=Quantity<Character>(count:1,thing:"🐔")lettwoDucks=Quantity<Character>(count:2,thing:"🦆")oneHen==twoDucks// false

Equality by Reference

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:

importFoundationclassObjCObject:NSObject{}ObjCObject()==ObjCObject()// false

For Swift reference types (that is, classes), equality can be evaluated using the identity equality operator (===):

classObject:Equatable{staticfunc==(lhs:Object,rhs:Object)->Bool{returnlhs===rhs}}Object()==Object()// false

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.

Comparable

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:

OperatorName
<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:

extensionBinomen:Comparable{staticfunc<(lhs:Binomen,rhs:Binomen)->Bool{iflhs.genus!=rhs.genus{returnlhs.genus<rhs.genus}else{returnlhs.species<rhs.species}}}🐻>🐺// true ("Ursus" lexicographically follows "Canis")

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.

Incomparable Limitations with No Equal

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

Comparable Benefits

Conforming to Comparable confers a multitude of benefits.

One such benefit is that arrays containing values of comparable types can call methods like sorted(), min(), and max():

let🐬=Binomen(genus:"Tursiops",species:"truncatus")let🌻=Binomen(genus:"Helianthus",species:"annuus")let🍄=Binomen(genus:"Amanita",species:"muscaria")let🐶=Binomen(genus:"Canis",species:"domesticus")letmenagerie=[🐺,🐻,🐬,🌻,🍄,🐶]menagerie.sorted()// [🍄, 🐶, 🐺, 🌻, 🐬, 🐻]menagerie.min()// 🍄menagerie.max()// 🐻

Having a defined ordering also lets you create ranges, like so:

letlessThan10=..<10lessThan10.contains(1)// truelessThan10.contains(11)// falseletoneToFive=1...5oneToFive.contains(3)// trueoneToFive.contains(7)// false

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.

Swift Import Declarations

$
0
0

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.

// Triathlon Modulefuncswim(){print("🏊‍ Swim 1.5 km")}funcbike(){print("🚴 Cycle 40 km")}funcrun(){print("🏃‍ Run 10 km")}

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.

importTriathlonswim()// OK, calls Triathlon.swimbike()// OK, calls Triathlon.bikerun()// OK, calls Triathlon.run

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.

importTriathlonimportPentathlonbike()// OK, calls Triathlon.bikefence()// OK, calls Pentathlon.fenceswim()// Error, 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.

Importing Individual Declarations

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:

KindDescription
structStructure
classClass
enumEnumeration
protocolProtocol
typealiasType Alias
funcFunction
letConstant
varVariable

For example, the following import declaration adds only the swim() function from the Pentathlon module:

importfuncPentathlon.swimswim()// OK, calls Pentathlon.swimfence()// Error, unresolved identifier

Resolving Symbol Name Collisions

When multiple symbols are referenced by the same name in code, the Swift compiler resolves this reference by consulting the following, in order:

  1. Local Declarations
  2. Imported Declarations
  3. 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)

Clarifying and Minimizing Scope

Beyond resolving name collisions, importing declarations can also be a way to clarify your intent as a programmer.

If you’re, for example, using only a single function from a mega-framework like AppKit, you might single that out in your import declaration.

importfuncAppKit.NSUserNameNSUserName()// "jappleseed"

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.

importfuncDarwin.fputsimportvarDarwin.stderrstructStderrOutputStream:TextOutputStream{mutatingfuncwrite(_string:String){fputs(string,stderr)}}varstandardError=StderrOutputStream()print("Error!",to:&standardError)

Importing a Submodule

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.

Dictionary Services

$
0
0

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.

Unix

Nearly all Unix distributions include a small collection newline-delimited list of words. On macOS, these can be found at /usr/share/dict:

$ls /usr/share/dict
            README
        connectives
        propernames
        web2
        web2a
            words@ -> web2
        

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.

macOS

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:

$ls"/Library/Dictionaries/New Oxford American Dictionary.dictionary/Contents"    Body.data
        DefaultStyle.css
        EntryID.data
        EntryID.index
        Images/
        Info.plist
        KeyText.data
        KeyText.index
        Resources/
        _CodeSignature/
        version.plist
        

A filesystem autopsy reveals some interesting implementation details. For New Oxford American Dictionary, in particular, its contents include:

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.

Getting the Definition of Words

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..."
#import <CoreServices/CoreServices.h>
        NSString*word=@"apple";NSString*definition=(__bridge_transferNSString*)DCSCopyTextDefinition(NULL,(__bridgeCFStringRef)word,CFRangeMake(0,[wordlength]));NSLog(@"%@",definition);

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!

Setting Active Dictionaries

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?”

Why yes. Yes there are.

Exploring 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:

externCFArrayRefDCSCopyAvailableDictionaries();externCFStringRefDCSDictionaryGetName(DCSDictionaryRefdictionary);externCFStringRefDCSDictionaryGetShortName(DCSDictionaryRefdictionary);externDCSDictionaryRefDCSDictionaryCreate(CFURLRefurl);externCFStringRefDCSDictionaryGetName(DCSDictionaryRefdictionary);externCFArrayRefDCSCopyRecordsForSearchString(DCSDictionaryRefdictionary,CFStringRefstring,void*,void*);externCFDictionaryRefDCSCopyDefinitionMarkup(DCSDictionaryRefdictionary,CFStringRefrecord);externCFStringRefDCSRecordCopyData(CFTypeRefrecord);externCFStringRefDCSRecordCopyDataURL(CFTypeRefrecord);externCFStringRefDCSRecordGetAnchor(CFTypeRefrecord);externCFStringRefDCSRecordGetAssociatedObj(CFTypeRefrecord);externCFStringRefDCSRecordGetHeadword(CFTypeRefrecord);externCFStringRefDCSRecordGetRawHeadword(CFTypeRefrecord);externCFStringRefDCSRecordGetString(CFTypeRefrecord);externCFStringRefDCSRecordGetTitle(CFTypeRefrecord);externDCSDictionaryRefDCSRecordGetSubDictionary(CFTypeRefrecord);

Private as they are, these functions aren’t about to start documenting themselves. So let’s take a look at how they’re used:

Getting Available Dictionaries

NSMapTable*availableDictionariesKeyedByName=[NSMapTablemapTableWithKeyOptions:NSPointerFunctionsCopyInvalueOptions:NSPointerFunctionsObjectPointerPersonality];for(iddictionaryin(__bridge_transferNSArray*)DCSCopyAvailableDictionaries()){NSString*name=(__bridgeNSString*)DCSDictionaryGetName((__bridgeDCSDictionaryRef)dictionary);[availableDictionariesKeyedByNamesetObject:dictionaryforKey:name];}

Getting Definition for Word

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:

NSString*word=@"apple";for(NSString*nameinavailableDictionariesKeyedByName){iddictionary=[availableDictionariesKeyedByNameobjectForKey:name];CFRangetermRange=DCSGetTermRangeInString((__bridgeDCSDictionaryRef)dictionary,(__bridgeCFStringRef)word,0);if(termRange.location==kCFNotFound){continue;}NSString*term=[wordsubstringWithRange:NSMakeRange(termRange.location,termRange.length)];NSArray*records=(__bridge_transferNSArray*)DCSCopyRecordsForSearchString((__bridgeDCSDictionaryRef)dictionary,(__bridgeCFStringRef)term,NULL,NULL);if(records){for(idrecordinrecords){NSString*headword=(__bridgeNSString*)DCSRecordGetHeadword((__bridgeCFTypeRef)record);if(headword){NSString*definition=(__bridge_transferNSString*)DCSCopyTextDefinition((__bridgeDCSDictionaryRef)dictionary,(__bridgeCFStringRef)headword,CFRangeMake(0,[headwordlength]));NSLog(@"%@: %@",name,definition);NSString*HTML=(__bridge_transferNSString*)DCSRecordCopyData((__bridgeDCSDictionaryRef)dictionary,(__bridgeCFStringRef)headword,CFRangeMake(0,[headwordlength]));NSLog(@"%@: %@",name,definition);}}}}

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.

Entry for apple in Dictionary.app

iOS

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.

You initialize it with the desired term:

UIReferenceLibraryViewController*referenceLibraryViewController=[[UIReferenceLibraryViewControlleralloc]initWithTerm:@"apple"];[viewControllerpresentViewController:referenceLibraryViewControlleranimated:YEScompletion:nil];

Presenting a UIReferenceLibraryViewController

This is the same behavior that one might encounter when tapping the “Define” menu item on a highlighted word in a text view.

Presenting a UIReferenceLibraryViewController

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.

[UIReferenceLibraryViewControllerdictionaryHasDefinitionForTerm:@"apple"];

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-sh

$
0
0

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.

Installation and Setup

Assuming you have Swift and the Xcode Developer Tools on your system, you can install swift-sh with Homebrew using the following command:

$ brew install mxcl/made/swift-sh
        

Example Usage

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:

♠ 10 9 8 7
♥ 6 5 4 3
♦ —
♣ 7 6 5 3 2
♠ 6 5 4 3 2

M

Meyer               Drax

Bond

♠ A K Q J
♥ 10 9 8 7 2♥ A K Q J
♦ J 10 2♦ A K
♣ —♣ K J 9
♠ —
♥ —
♦ Q 8 7 6 5 4 3 2
♣ A Q 10 8 4

Importing Dependencies

Start by creating a new .swift file with a shebang at the beginning (more on that later).

$echo"#!/usr/bin/swift sh"> bridge.swift
        

Next, add import declarations for three modules: DeckOfPlayingCards, PlayingCard, and Cycle.

importDeckOfPlayingCards// @NSHipster ~> 4.0.0importPlayingCardimportCycle// @NSHipster == bb11e28

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.

Enlisting Players

With all of our dependencies accounted for, we have everything we need to write our script.

First, create a Player class, consisting of name and hand properties.

classPlayer{varname:Stringvarhand:[PlayingCard]=[]init(name:String){self.name=name}}

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.

extensionPlayer:CustomStringConvertible{vardescription:String{vardescription="\(name):"letcardsBySuit=Dictionary(grouping:hand){$0.suit}for(suit,cards)incardsBySuit.sorted(by:{$0.0>$1.0}){description+="\t\(suit)"description+=cards.sorted(by:>).map{"\($0.rank)"}.joined(separator:"")description+="\n"}returndescription}}

Shuffling and Dealing the Deck

Create and shuffle a standard deck of 52 playing cards, and initialize players at each of the four cardinal directions.

vardeck=Deck.standard52CardDeck()deck.shuffle()varnorth=Player(name:"North")varwest=Player(name:"West")vareast=Player(name:"East")varsouth=Player(name:"South")

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.

letplayers=[north,east,west,south]varround=players.cycled()whileletcard=deck.deal(),letplayer=round.next(){player.hand.append(card)}

After the cards are dealt, each player has 13 cards.

forplayerinplayers{print(player)}

Running the Card Game

We can run our completed Swift script from the command line by passing the file as an argument to the swift sh subcommand.

$ swift sh ./bridge.swift
        North:	♠︎ K 10 9 8 5 4
        ♡ K 3
        ♢ A 10
        ♣︎ A 4 2
        West:	♠︎ J 3 2
        ♡ 9 6 5
        ♢ Q 9 8 3 2
        ♣︎ 6 5
        East:	♠︎ Q 7
        ♡ A J 10 7 4
        ♢ K 5 4
        ♣︎ 10 7 3
        South:	♠︎ A 6
        ♡ Q 8 2
        ♢ J 7 6
        ♣︎ K Q J 9 8
        

Smashing!

Making an Executable

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.

$mv bridge.swift bridge
        $chmod +x bridge
        $ ./bridge
        

Current Limitations

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:

Dependency on GitHub

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

Module Names Must Match Repository Names

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).

Lack of Support for Import Declaration Syntax

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.

Swift GYB

$
0
0

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:

%{codable_types=['Bool','String','Double','Float','Int','Int8','Int16','Int32','Int64','UInt','UInt8','UInt16','UInt32','UInt64']}%

Later on, in the implementation of SingleValueEncodingContainer, these types are iterated over to generate the methods declarations for the protocol’s requirements:

%fortypeincodable_types:mutatingfuncencode(_value:${type})throws%end

Evaluating the GYB template results in the following declarations:

mutatingfuncencode(_value:Bool)throwsmutatingfuncencode(_value:String)throwsmutatingfuncencode(_value:Double)throwsmutatingfuncencode(_value:Float)throwsmutatingfuncencode(_value:Int)throwsmutatingfuncencode(_value:Int8)throwsmutatingfuncencode(_value:Int16)throwsmutatingfuncencode(_value:Int32)throwsmutatingfuncencode(_value:Int64)throwsmutatingfuncencode(_value:UInt)throwsmutatingfuncencode(_value:UInt8)throwsmutatingfuncencode(_value:UInt16)throwsmutatingfuncencode(_value:UInt32)throwsmutatingfuncencode(_value:UInt64)throws

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:

$wc-l Codable.swift.gyb
        2183 Codable.swift.gyb
        $wc-l Codable.swift
        5790 Codable.swift
        

Installing GYB

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):

$ wget https://github.com/apple/swift/raw/master/utils/gyb
        $ wget https://github.com/apple/swift/raw/master/utils/gyb.py
        $chmod +x 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:

find .-name'*.gyb' |                                               \while read file;do\
        ./path/to/gyb --line-directive''-o"${file%.gyb}""$file";\done

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:

%{abilities=['strength','dexterity','constitution','intelligence','wisdom','charisma']}classCharacter{varname:String%forabilityinabilities:var${type}:Int%end}

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.

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:

%{importcsv}%withopen('path/to/file.csv')asfile:%forrowincsv.DictReader(file):

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.

TextOutputStream

$
0
0

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.

For instance, did you know that the actual signature of print is print(_:separator:terminator:)? Or that it had a variant named print(_:separator:terminator:to:)?

Shocking, I know.

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:

funcprint<Target>(_items:Any...,separator:String=default,terminator:String=default,tooutput:inoutTarget)whereTarget:TextOutputStream

This overload of print takes a variable-length list of arguments, followed by separator and terminator parameters — both of which have default values.

  • separator is the string used to join the representation of each element in items into a single string. By default, this is a space ("").
  • terminator is the string appended to the end of the printed representation. By default, this is a newline ("\n").

The last parameter, output takes a mutable instance of a generic Target type that conforms to the TextOutputStream protocol.

An instance of a type adopting to TextOutputStream can be passed to the print(_:to:) function to capture and redirect strings from standard output.

Implementing a Custom Text Output Stream Type

Due to the mercurial nature of Unicode, you can’t know what characters lurk within a string just by looking at it. Between combining marks, format characters, unsupported characters, variation sequences, ligatures, digraphs, and other presentational forms, a single extended grapheme cluster can contain much more than meets the eye.

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.

protocolTextOutputStream{mutatingfuncwrite(_string:String)}

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).

structUnicodeLogger:TextOutputStream{mutatingfuncwrite(_string:String){guard!string.isEmpty&&string!="\n"else{return}for(index,unicodeScalar)instring.unicodeScalars.lazy.enumerated(){letname=unicodeScalar.name??""letcodePoint=String(format:"U+%04X",unicodeScalar.value)print("\(index): \(unicodeScalar)\(codePoint)\t\(name)")}}}

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!

print("👨‍👩‍👧‍👧")// Prints: "👨‍👩‍👧‍👧"varlogger=UnicodeLogger()print("👨‍👩‍👧‍👧",to:&logger)// Prints:// 0: 👨 U+1F468    MAN// 1:    U+200D     ZERO WIDTH JOINER// 2: 👩 U+1F469    WOMAN// 3:    U+200D     ZERO WIDTH JOINER// 4: 👧 U+1F467    GIRL// 5:    U+200D     ZERO WIDTH JOINER// 6: 👧 U+1F467    GIRL

Ideas for Using Custom Text Output Streams

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:

Logging to Standard Error

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:

importfuncDarwin.fputsimportvarDarwin.stderrstructStderrOutputStream:TextOutputStream{mutatingfuncwrite(_string:String){fputs(string,stderr)}}varstandardError=StderrOutputStream()print("Error!",to:&standardError)

Writing Output to a File

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).

importFoundationstructFileHandlerOutputStream:TextOutputStream{privateletfileHandle:FileHandleletencoding:String.Encodinginit(_fileHandle:FileHandle,encoding:String.Encoding=.utf8){self.fileHandle=fileHandleself.encoding=encoding}mutatingfuncwrite(_string:String){ifletdata=string.data(using:encoding){fileHandle.write(data)}}}

Following this approach, you can customize print to write to a file instead of a stream.

leturl=URL(fileURLWithPath:"/path/to/file.txt")letfileHandle=tryFileHandle(forWritingTo:url)varoutput=FileHandlerOutputStream(fileHandle)print("\(Date())",to:&output)

Escaping Streamed Output

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 "&lt;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!


ExpressibleByStringInterpolation

$
0
0

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?

letformatter=DateFormatter()formatter.dateFormat="YYYY-MM-dd"formatter.string(from:Date())// 2019-02-04 (🤨)

It sure looks that way, at least for the first 360-ish days of the year. But what if we jump to the last day of the year?

letdateComponents=DateComponents(year:2019,month:12,day:31)letdate=Calendar.current.date(from:dateComponents)!formatter.string(from:date)// 2020-12-31 (😱)

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.

What we actually want is "yyyy".

formatter.dateFormat="yyyy-MM-dd"formatter.string(from:date)// 2019-12-31 (😄)

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:

"\(date,component:.year)-\(date,component:.month)-\(date,component:.day)"// "2019-12-31"

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.

importFoundationextensionDefaultStringInterpolation{mutatingfuncappendInterpolation(_value:Date,formatter:DateFormatter){self.appendInterpolation(formatter.string(from:value))}mutatingfuncappendInterpolation<T>(_value:T,formatter:NumberFormatter)whereT:Numeric{self.appendInterpolation(formatter.string(from:valueas!NSNumber)!)}}

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.

structXMLEscapedString:LosslessStringConvertible{varvalue:Stringinit?(_value:String){self.value=value}vardescription:String{returnself.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.

importFoundationextensionXMLEscapedString:ExpressibleByStringInterpolation{init(stringLiteralvalue:String){self.init(value)!}init(stringInterpolation:StringInterpolation){self.init(stringInterpolation.value)!}structStringInterpolation:StringInterpolationProtocol{varvalue:String=""init(literalCapacity:Int,interpolationCount:Int){self.value.reserveCapacity(literalCapacity)}mutatingfuncappendLiteral(_literal:String){self.value.append(literal)}mutatingfuncappendInterpolation<T>(_value:T)whereT:CustomStringConvertible{letescaped=CFXMLCreateStringByEscapingEntities(nil,value.descriptionasNSString,nil)!asNSStringself.value.append(escapedasString)}}}

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!)

letname="<bobby>"letmarkup:XMLEscapedString="""<p>Hello, \(name)!</p>
        """print(markup)// <p>Hello, &lt;bobby&gt;!</p>

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:

varinterpolation=XMLEscapedString.StringInterpolation(literalCapacity:15,interpolationCount:1)interpolation.appendLiteral("<p>Hello, ")interpolation.appendInterpolation(name)interpolation.appendLiteral("!</p>")letmarkup=XMLEscapedString(stringInterpolation:interpolation)// <p>Hello, &lt;bobby&gt;!</p>

Reads just like poetry, doesn’t it?


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.

Regular Expressions in Swift

$
0
0

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:

  1. You don’t need NSRegularExpression to use regular expressions in Swift.
  2. 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:

letpattern=#"""(?xi)(?<suspect>    ((Miss|Ms.) \h Scarlett?) |    ((Colonel | Col.) \h Mustard) |    ((Reverend | Mr.) \h Green) |    (Mrs. \h Peacock) |    ((Professor | Prof.) \h Plum) |    ((Mrs. \h White) | ((Doctor | Dr.) \h Orchid))),?(?-x: in the )(?<location>    Kitchen        | Ballroom | Conservatory |    Dining \h Room      |       Library      |    Lounge         | Hall     | Study),?(?-x: with the )(?<weapon>      Candlestick    | Knife    | (Lead(en)?\h)? Pipe    | Revolver    | Rope    | Wrench)"""#letregex=tryNSRegularExpression(pattern:pattern,options:[])

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.

letnsrange=NSRange(suggestion.startIndex..<suggestion.endIndex,in:suggestion)ifletmatch=regex.firstMatch(in:suggestion,options:[],range:nsrange){forcomponentin["suspect","location","weapon"]{letnsrange=match.range(withName:component)ifnsrange.location!=NSNotFound,letrange=Range(nsrange,in:suggestion){print("\(component): \(suggestion[range])")}}}// Prints:// "suspect: Professor Plum"// "location: Dining Room"// "weapon: Candlestick"

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.

API Pollution in Swift Modules

$
0
0

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).

Import the NaturalLanguage framework, and *boom*— your app can determine the language of text; import CoreMotion, and — *whoosh*— your app can respond to changes in device orientation.

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.

letoneTwoThree:[Int]=[1,2,3]letfourFiveSix:[Int]=[4,5,6]oneTwoThree+fourFiveSix// [1, 2, 3, 4, 5, 6]

If we look at the operator’s declaration in the standard library, we see that it’s provided in an unqualified extension on Array:

extensionArray{@inlinablepublicstaticfunc+(lhs:Array,rhs:Array)->Array{}}

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:

extensionArraywhereElement:Numeric{publicstaticfunc+(lhs:Array,rhs:Array)->Array{returnArray(zip(lhs,rhs).map{$0+$1})}}oneTwoThree+fourFiveSix// [5, 7, 9] 😕

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:

@discardableResultfuncdump<T>(_value:T,name:String?=nil,indent:Int=0,maxDepth:Int=.max,maxItems:Int=.max)->T

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.

publicfuncdump(_string:String){print(string.utf8.map{$0})}

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.

dump("🏭💨")// [240, 159, 143, 173, 240, 159, 146, 168]

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.

letname="Swift"letgreeting="Hello, \(name)!"// "Hello, Swift!"

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:

extensionDefaultStringInterpolation{publicmutatingfuncappendInterpolation<T>(_value:T)whereT:StringProtocol{self.appendInterpolation(value.uppercased()asTextOutputStreamable)}}

StringProtocol inherits, among other things the TextOutputStreamable and CustomStringConvertible protocols, making it more specific than the appendInterpolation method declared by DefaultStringInterpolation that would otherwise be invoked when interpolating String values.

publicstructDefaultStringInterpolation:StringInterpolationProtocol{@inlinablepublicmutatingfuncappendInterpolation<T>(_value:T)whereT:TextOutputStreamable,T:CustomStringConvertible{}}

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.

dump("🏭💨")// [240, 159, 143, 173, 240, 159, 146, 168]dump("🏭💨"asCustomStringConvertible)// "🏭💨"

Scoped Import Declarations

Fork Dependencies

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 .+:

infixoperator.+:AdditionPrecedenceextensionArraywhereElement:Numeric{staticfunc.+(lhs:Array,rhs:Array)->Array{returnArray(zip(lhs,rhs).map{$0+$1})}}oneTwoThree+fourFiveSix// [1, 2, 3, 4, 5, 6]oneTwoThree.+fourFiveSix// [5, 7, 9]

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.

JavaScriptCore

$
0
0

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.

importJavaScriptCoreletcontext=JSContext()!letresult=context.evaluateScript("1 + 2 + 3")result?.toInt32()// 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 TypeJSValue methodObjective-C TypeSwift Type
stringtoStringNSStringString!
booleantoBoolBOOLBool
numbertoNumber
toDouble
toInt32
toUInt32
NSNumber
double
int32_t
uint32_t
NSNumber!
Double
Int32
UInt32
DatetoDateNSDateDate?
ArraytoArrayNSArray[Any]!
ObjecttoDictionaryNSDictionary[AnyHashable: Any]!
ClasstoObject
toObjectOfClass:
custom typecustom 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.

letqueue=DispatchQueue(label:"js")letvm=queue.sync{JSVirtualMachine()!}letcontext=JSContext(virtualMachine:vm)!

Getting JavaScript Context Values from Swift

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.

context.evaluateScript("var threeTimesFive = triple(5)")context.objectForKeyedSubscript("threeTimesFive")?.toInt32()// 15

Setting Swift Values on a JavaScript Context

Conversely, you can set Swift values as variables in a JSContext by calling the setObject(_:forKeyedSubscript:) method.

letthreeTimesTwo=2*3context.setObject(threeTimesTwo,forKeyedSubscript:"threeTimesTwo"asNSString)

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.

context.evaluateScript("threeTimesTwo === triple(2);")?.toBool()// true

Passing Functions between Swift and 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.

lettriple=context.objectForKeyedSubscript("triple")triple?.call(withArguments:[9])?.toInt32()// 27

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.

letquadruple:@convention(block)(Int)->Int={inputinreturninput*4}context.setObject(quadruple,forKeyedSubscript:"quadruple"asNSString)

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.

context.evaluateScript("quadruple(3)")?.toInt32()// 12context.objectForKeyedSubscript("quadruple")?.call(withArguments:[3])// 12

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:).

context.setObject(Person.self,forKeyedSubscript:"Person"asNSString)

Instantiating Swift Classes from JavaScript

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.

context.evaluateScript(#"""function loadPeople(json) {    return JSON.parse(json)               .map((attributes) => {        let person = Person.createWithFirstNameLastName(            attributes.first,            attributes.last        );        person.birthYear = attributes.year;        return person;    });}"""#)

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).

letjson="""
        [
        { "first": "Grace", "last": "Hopper", "year": 1906 },
        { "first": "Ada", "last": "Lovelace", "year": 1815 },
        { "first": "Margaret", "last": "Hamilton", "year": 1936 }
        ]
        """letloadPeople=context.objectForKeyedSubscript("loadPeople")!letpeople=loadPeople.call(withArguments:[json])!.toArray()

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.

guardleturl=Bundle.main.url(forResource:"mustache",withExtension:"js")else{fatalError("missing resource mustache.js")}context.evaluateScript(tryString(contentsOf:url),withSourceURL:url)

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.

Swift Code Formatters

$
0
0

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:

ProjectRepository URL
SwiftFormathttps://github.com/nicklockwood/SwiftFormat
SwiftLinthttps://github.com/realm/SwiftLint
Prettier with Swift Pluginhttps://github.com/prettier/prettier
swift-format (proposed)https://github.com/google/swift/tree/format

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.

Installation

SwiftFormat is distributed via Homebrew as well as Mint and CocoaPods.

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:

$ brew install swiftlint
        

Alternatively, you can install SwiftLint with CocoaPods, Mint, or as a standalone installer package (.pkg).

Usage

To use SwiftLint as a code formatter, run the autocorrect subcommand passing the --format option and the files or directories to correct.

$ swiftlint autocorrect --format--path Example.swift
        

Example Formatting

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.

Thanks to its (in-development) plugin architecture, the same line-breaking behavior can be applied to other languages, including Swift.

Installation

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.

Installation

The code for swift-format is currently hosted on the format branch of Google’s fork of the Swift project. You can check it out and build it from source by running the following commands:

$ git clone https://github.com/google/swift.git swift-format
        $cd swift-format
        $ git submodule update --init$ swift build
        

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.

$ swift-format -m dump-configuration .swift-format.json
        

Running the command above populates the specified file with the following JSON:

{"blankLineBetweenMembers":{"ignoreSingleLineProperties":true},"indentation":{"spaces":2},"lineLength":100,"maximumBlankLines":1,"respectsExistingLineBreaks":true,"tabWidth":8,"version":1}

After fiddling with the configuration — such as setting lineLength to the correct value of 80 (don’t @ me)— you can apply it thusly:

$ swift-format Example.swift --configuration .swift-format.json
        

Example Output

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.

MapKit JS

$
0
0

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).

Register a Maps ID

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.

Create a New MapKit JS Key

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.

Download Key

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:

-----BEGIN PRIVATE KEY-----
        MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg84Z+p4rGieL6YiCO
        DxAeH0BcSZprk99Dl1UWMOODbHagCgYIKoZIzj0DAQehRANCAARDijSXDV6xjH0N
        CbPelVcWUfWG80nadLsuaGOcsrixyPaKlEdzsBeypOZfxbLM3glKoZCCLUjF/WGd
        Ho0RMbco
        -----END PRIVATE KEY-----
        

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:

Encoded Form Base-64 Encoded JSON

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjRUOTJZWlNXR00ifQ.eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjRUOTJZWlNXR00ifQ.eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjRUOTJZWlNXR00ifQ

Header Algorithm and Token Type

{"typ":"JWT","alg":"ES256","kid":"4T92YZSWGM"}

Payload Data

{"iss":"9JWVADR3RQ","exp":1552393165.925637,"iat":1552306765.925637}

Signature Verification of Identity

ECDSASHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),fs.readSync("key.pub"),fs.readSync("key.p8"));

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.

importFoundationimportSwiftJWTletheader=Header(typ:"JWT",kid:"KEY ID")letclaims=ClaimsStandardJWT(iss:"TEAM ID",exp:Date(timeIntervalSinceNow:86400),iat:Date())varjwt=JWT(header:header,claims:claims)letp8="""
        -----BEGIN PRIVATE KEY-----
        MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg84Z+p4rGieL6YiCO
        DxAeH0BcSZprk99Dl1UWMOODbHagCgYIKoZIzj0DAQehRANCAARDijSXDV6xjH0N
        CbPelVcWUfWG80nadLsuaGOcsrixyPaKlEdzsBeypOZfxbLM3glKoZCCLUjF/WGd
        Ho0RMbco
        -----END PRIVATE KEY-----
        """.data(using:.utf8)!letsignedToken=tryjwt.sign(using:.es256(privateKey:p8))/* "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjRUOTJZWlNXR00ifQ.\
        eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjRUOTJZWlNXR00ifQ.\
        eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjRUOTJZWlNXR00ifQ"  */

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.

importVaporimportSwiftJWTpublicfuncroutes(_router:Router)throws{router.get{reqinreturntryreq.view().render("map")}router.get("token"){req->StringinreturntrygenerateSignedJWTToken()}}

Initializing MapKit in JavaScript

Finally, let’s complete le grand tour with a peek at what’s going on in our client-side JavaScript file:

mapkit.init({authorizationCallback:done=>{fetch("/token").then(res=>res.text()).then(token=>done(token)).catch(error=>{console.error(error);});}});

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:

consttoken="...";mapkit.init({authorizationCallback:done=>{done(token);}});

At Last, a Map!

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.

constcenter=newmapkit.Coordinate(37.3327,-122.0053),span=newmapkit.CoordinateSpan(0.0125,0.0125),region=newmapkit.CoordinateRegion(center,span);letmap=newmapkit.Map("map",{region:region,showsCompass:mapkit.FeatureVisibility.Hidden,showsZoomControl:false,showsMapTypeControl:false});

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!

LocalizedError, RecoverableError, CustomNSError

$
0
0

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.

-(NSURL*)replaceItemAtURL:(NSURL*)urloptions:(NSFileVersionReplacingOptions)optionserror:(NSError*_Nullable*)error;

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.

protocolLocalizedError:Error{varerrorDescription:String?{get}varfailureReason:String?{get}varrecoverySuggestion:String?{get}varhelpAnchor:String?{get}}

These properties map 1:1 with familiar NSErroruserInfo keys.

RequirementUser Info Key
errorDescriptionNSLocalizedDescriptionKey
failureReasonNSLocalizedFailureReasonErrorKey
recoverySuggestionNSLocalizedRecoverySuggestionErrorKey
helpAnchorNSHelpAnchorErrorKey

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...}}}

iOS alert modal


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.

Default macOS error modal

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.

extensionAppDelegate{// MARK: NSErrorRecoveryAttemptingoverridefuncattemptRecovery(fromErrorerror:Error,optionIndexrecoveryOptionIndex:Int)->Bool{do{switchrecoveryOptionIndex{case 0: // Try, try again
        trysomething()case 1:
        fallthroughdefault:break}}catch{window.presentError(error)}returntrue}}

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.

Recoverable macOS error modal

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:

protocolRecoverableError:Error{varrecoveryOptions:[String]{get}funcattemptRecovery(optionIndexrecoveryOptionIndex:Int,resultHandlerhandler:@escaping(Bool)->Void)funcattemptRecovery(optionIndexrecoveryOptionIndex:Int)->Bool}

Also like LocalizedError, these requirements map onto error userInfo keys (albeit not as directly).

RequirementUser Info Key
recoveryOptionsNSLocalizedRecoveryOptionsErrorKey
attemptRecovery(optionIndex:_:)
attemptRecovery(optionIndex:)
NSRecoveryAttempterErrorKey*

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.

protocolErrorRecoveryDelegate:class{associatedtypeRecoveryOption:CustomStringConvertible,CaseIterablefuncattemptRecovery(fromerror:Error,withoption:RecoveryOption)->Bool}

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.

structDelegatingRecoverableError<Delegate,Error>:RecoverableErrorwhereDelegate:ErrorRecoveryDelegate,Error:Swift.Error{leterror:Errorweakvardelegate:Delegate?=nilinit(recoveringFromerror:Error,withdelegate:Delegate?){self.error=errorself.delegate=delegate}varrecoveryOptions:[String]{returnDelegate.RecoveryOption.allCases.map{"\($0)"}}funcattemptRecovery(optionIndexrecoveryOptionIndex:Int)->Bool{letrecoveryOptions=Delegate.RecoveryOption.allCasesletindex=recoveryOptions.index(recoveryOptions.startIndex,offsetBy:recoveryOptionIndex)letoption=Delegate.RecoveryOption.allCases[index]returnself.delegate?.attemptRecovery(from:self.error,with:option)??false}}

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?

Recoverable macOS error modal with Unintelligible title

…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.

protocolCustomNSError:Error{staticvarerrorDomain:String{get}varerrorCode:Int{get}varerrorUserInfo:[String:Any]{get}}

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:

extensionDelegatingRecoverableError:CustomNSError{varerrorUserInfo:[String:Any]{return(self.errorasNSError).userInfo}}

With this one additional step, we can now enjoy the fruits of our burden.

Recoverable macOS error modal


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.

Such is the error of our ways.


XcodeKit and Xcode Source Editor Extensions

$
0
0

When we last wrote about extending Xcode in 2014, we were living in a golden age, and didn’t even know 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:

Flow diagram for extension installation process

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.

Screenshot of adding Source Editor Extension target to Xcode project

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 textPropertyDefinition
Extension name, as shown in System PreferencesBundle Display NameInfo.plist
Top level menu item for extensionBundle NameInfo.plist
Individual menu commandXCSourceEditorCommandNameInfo.plist
 XCSourceEditorCommandDefinitionKey.nameKeyRuntime

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:

Screenshot showing macOS dock with blue and grey Xcode icons

Sometimes attaching to the debugger fails silently, and it’s a good idea to set a log or audible breakpoint to track this:

funcextensionDidFinishLaunching(){os_log("Extension ready",type:.debug)}

Extension Scheme Setup

Two suggestions from Daniel Jalkut to make life easier.
Firstly add Xcode as the default executable in the Extension scheme’s Run/Info pane:

Screenshot showing Xcode set as default executable in extension scheme

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:

Screenshot showing path to sample code under argument passed on launch in extension scheme

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:

Screenshot showing Developer Frameworks Directory added to Runpath and Framework Search Paths in test target's 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:

$ pluginkit -m-p com.apple.dt.Xcode.extension.source-editor
        
        +    com.apple.dt.XCDocumenter.XCDocumenterExtension(1.0)
        +    com.apple.dt.XcodeBuiltInExtensions(10.2)
        com.Swiftify.Xcode.Extension(4.6.1)
        +    com.charcoaldesign.SwiftFormat-for-Xcode.SourceEditorExtension(0.40.3)
        !    com.hotbeverage.accesscontrolkitty.extension(1.0.1)
        

The leading flags in the output can give you some clues as to what might be happening:

  • +seems to indicate a specifically enabled extension
  • - indicates a disabled extension
  • ! indicates some form of conflict

For extra verbose output that lists any duplicates:

$ pluginkit -m-p com.apple.dt.Xcode.extension.source-editor -A-D-vvv

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):

$PATH=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support:"$PATH"$ lsregister -f /Applications/Xcode.app
        

Features and Caveats

Transforming Source Code

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 🤞.

Swift

$
0
0

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”

Chaetura hipsterus

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.

Swift GYB

$
0
0

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:

%{codable_types=['Bool','String','Double','Float','Int','Int8','Int16','Int32','Int64','UInt','UInt8','UInt16','UInt32','UInt64']}%

Later on, in the implementation of SingleValueEncodingContainer, these types are iterated over to generate the methods declarations for the protocol’s requirements:

%fortypeincodable_types:mutatingfuncencode(_value:${type})throws%end

Evaluating the GYB template results in the following declarations:

mutatingfuncencode(_value:Bool)throwsmutatingfuncencode(_value:String)throwsmutatingfuncencode(_value:Double)throwsmutatingfuncencode(_value:Float)throwsmutatingfuncencode(_value:Int)throwsmutatingfuncencode(_value:Int8)throwsmutatingfuncencode(_value:Int16)throwsmutatingfuncencode(_value:Int32)throwsmutatingfuncencode(_value:Int64)throwsmutatingfuncencode(_value:UInt)throwsmutatingfuncencode(_value:UInt8)throwsmutatingfuncencode(_value:UInt16)throwsmutatingfuncencode(_value:UInt32)throwsmutatingfuncencode(_value:UInt64)throws

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:

$wc-l Codable.swift.gyb
        2183 Codable.swift.gyb
        $wc-l Codable.swift
        5790 Codable.swift
        

Installing GYB

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):

$ wget https://github.com/apple/swift/raw/master/utils/gyb
        $ wget https://github.com/apple/swift/raw/master/utils/gyb.py
        $chmod +x 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:

find .-name'*.gyb' |                                               \while read file;do\
        ./path/to/gyb --line-directive''-o"${file%.gyb}""$file";\done

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:

%{abilities=['strength','dexterity','constitution','intelligence','wisdom','charisma']}%classCharacter{varname:String%forabilityinabilities:var${ability}:Int%end}

Evaluating this with GYB produces the following Swift code:

classCharacter{varname:Stringvarstrength:Intvardexterity:Intvarconstitution:Intvarintelligence:Intvarwisdom:Intvarcharisma:Int}

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:

%{importcsv}%withopen('path/to/file.csv')asfile:%forrowincsv.DictReader(file):

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.

UITableViewHeaderFooterView

$
0
0

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:

structSection{lettitle:Stringletimage:UIImageletrows:[(year:Int,amount:Decimal)]letnotes:String?}letbutter=Section(title:"Butter",image:#imageLiteral(resourceName:"Butter"),rows:[...],notes:nil)// etc.letsections:[Section]=[milk,yogurt,butter,cheese,cottageCheese,condensedMilk,iceCream,whey]

In the view controller itself, the implementation for UITableViewDataSource delegate methods is cool and refreshing:

importUIKitfinalclassViewController:UIViewController{@IBOutletvartableView:UITableView!}// MARK: - UITableViewDataSourceextensionViewController:UITableViewDataSource{funcnumberOfSections(intableView:UITableView)->Int{returnsections.count}functableView(_tableView:UITableView,numberOfRowsInSectionsection:Int)->Int{returnsections[section].rows.count}functableView(_tableView:UITableView,cellForRowAtindexPath:IndexPath)->UITableViewCell{letcell=tableView.dequeueReusableCell(withIdentifier:"Cell",for:indexPath)letsection=sections[indexPath.section]letrow=section.rows[indexPath.row]cell.textLabel?.text="\(row.year)"cell.detailTextLabel?.text="\(row.amount)"returncell}}

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:):

importUIKitfinalclassSectionHeaderView:UITableViewHeaderFooterView{staticletreuseIdentifier:String=String(describing:self)varimageView:UIImageViewoverrideinit(reuseIdentifier:String?){super.init(reuseIdentifier:reuseIdentifier)imageView=UIImageView()contentView.addSubview(imageView)imageView.translatesAutoresizingMaskIntoConstraints=falseimageView.widthAnchor.constraint(equalToConstant:24.0).isActive=trueimageView.heightAnchor.constraint(equalToConstant:24.0).isActive=trueimageView.trailingAnchor.constraint(equalTo:contentView.layoutMarginsGuide.trailingAnchor).isActive=trueimageView.bottomAnchor.constraint(equalTo:contentView.layoutMarginsGuide.bottomAnchor).isActive=true}requiredinit?(coderaDecoder:NSCoder){super.init(coder:aDecoder)}}

In our view controller, we register the custom section header in viewDidLoad() by calling the register(_:forHeaderFooterViewReuseIdentifier:) method on tableView:

importUIKitfinalclassViewController:UIViewController{@IBOutletvartableView:UITableView!// MARK: UIViewControlleroverridefuncviewDidLoad(){super.viewDidLoad()self.tableView.register(SectionHeaderView.self,forHeaderFooterViewReuseIdentifier:SectionHeaderView.reuseIdentifier)}}

Option 2: Designing the View in Interface Builder

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.

importUIKitfinalclassViewController:UIViewController{@IBOutletvartableView:UITableView!// MARK: UIViewControlleroverridefuncviewDidLoad(){super.viewDidLoad()self.tableView.register(SectionHeaderView.nib,forHeaderFooterViewReuseIdentifier:SectionHeaderView.reuseIdentifier)}}

Implementing UITableViewDelegate Methods

From here, it’s smooth scrollingsailing. Enjoy your victory lap as you implement the requisite UITableViewDelegate methods:

importUIKit// MARK: - UITableViewDelegateextensionViewController:UITableViewDelegate{functableView(_tableView:UITableView,titleForHeaderInSectionsection:Int)->String?{returnsections[section].title}functableView(_tableView:UITableView,titleForFooterInSectionsection:Int)->String?{returnsections[section].notes}functableView(_tableView:UITableView,viewForHeaderInSectionsection:Int)->UIView?{guardletview=tableView.dequeueReusableHeaderFooterView(withIdentifier:SectionHeaderView.reuseIdentifier)as?SectionHeaderViewelse{returnnil}view.textLabel?.text=sections[section].titleview.imageView?.image=sections[section].imagereturnview}}

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.

Guided Access

$
0
0

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:

enumRestriction:String,CaseIterable{case purchase = "com.nshipster.example.restrictions.purchase"}

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:

  • guidedAccessRestrictionIdentifiers
  • textForGuidedAccessRestriction(withIdentifier:)
  • detailTextForGuidedAccessRestriction(withIdentifier:)(optional)
  • guidedAccessRestriction(withIdentifier:didChange:)

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.

importUIKitextensionRestriction{vartext:String{switchself{case .purchase:
        returnNSLocalizedString("Purchase",comment:"Text for Guided Access purchase restriction")}}}// MARK: - UIGuidedAccessRestrictionDelegateextensionAppDelegate:UIGuidedAccessRestrictionDelegate{varguidedAccessRestrictionIdentifiers:[String]?{returnRestriction.allCases.map{$0.rawValue}}functextForGuidedAccessRestriction(withIdentifierrestrictionIdentifier:String)->String?{returnRestriction(rawValue:restrictionIdentifier)?.text}// ...}

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:

extensionUIAccessibility{staticletguidedAccessDidAllowRestrictionNotification=NSNotification.Name("com.nshipster.example.notification.allow-restriction")staticletguidedAccessDidDenyRestrictionNotification=NSNotification.Name("com.nshipster.example.notification.deny-restriction")}

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:

importUIKitclassProductViewController:UIViewController{@IBOutletvarpurchaseButton:UIButton!// MARK: UIViewControlleroverridefuncawakeFromNib(){super.awakeFromNib()self.updateViewForGuidedAccess()}overridefuncviewDidLoad(){super.viewDidLoad()letselector=#selector(updateViewForGuidedAccess)letnames:[Notification.Name]=[UIAccessibility.guidedAccessStatusDidChangeNotification,UIAccessibility.guidedAccessDidAllowRestrictionNotification,UIAccessibility.guidedAccessDidDenyRestrictionNotification]fornameinnames{NotificationCenter.default.addObserver(self,selector:selector,name:name,object:nil)}}// MARK: -@objcprivatefuncupdateViewForGuidedAccess(){guardUIAccessibility.isGuidedAccessEnabledelse{return}switchUIAccessibility.guidedAccessRestrictionState(forIdentifier:Restriction.purchase.rawValue){case .allow:
        purchaseButton.isEnabled=truepurchaseButton.isHidden=falsecase .deny:
        purchaseButton.isEnabled=falsepurchaseButton.isHidden=true@unknowndefault:break}}}

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.

Viewing all 382 articles
Browse latest View live