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

Xcode Build Configuration Files

$
0
0

Software development best practicesprescribe strict separation of configuration from code. Yet developers on Apple platforms often struggle to square these guidelines with Xcode’s project-heavy workflow.

Understanding what each project setting does and how they all interact with one another is a skill that can take years to hone. And the fact that much of this information is buried deep within the GUIs of Xcode does us no favors.

Navigate to the “Build Settings” tab of the project editor, and you’ll be greeted by hundreds of build settings spread across layers of projects, targets, and configurations — and that’s to say nothing of the other six tabs!

Xcode build settings

Fortunately, there’s a better way to manage all of this configuration that doesn’t involve clicking through a maze of tabs and disclosure arrows.

This week, we’ll show you how you can use text-based xcconfig files to externalize build settings from Xcode to make your projects more compact, comprehensible, and powerful.


Xcode build configuration files, more commonly known by their xcconfig file extension, allow build settings for your app to be declared and managed without Xcode. They’re plain text, which means they’re much friendlier to source control systems and can be modified with any editor.

Fundamentally, each configuration file consists of a sequence of key-value assignments with the following syntax:

BUILD_SETTING_NAME=value

For example, to specify the Swift language version for a project, you’d specify the SWIFT_VERSION build setting like so:

SWIFT_VERSION=5.0

At first glance,xcconfig files bear a striking resemblance to .env files, with their simple, newline-delimited syntax. But there’s more to Xcode build configuration files than meets the eye. Behold!

Retaining Existing Values

To append rather than replace existing definitions, use the $(inherited) variable like so:

BUILD_SETTING_NAME=$(inherited)additional value

You typically do this to build up lists of values, such as the paths in which the compiler searches for frameworks to find included header files (FRAMEWORK_SEARCH_PATHS):

FRAMEWORK_SEARCH_PATHS=$(inherited)$(PROJECT_DIR)

Xcode assigns inherited values in the following order (from lowest to highest precedence):

  • Platform Defaults
  • Xcode Project xcconfig File
  • Xcode Project File Build Settings
  • Target xcconfig File
  • Target Build Settings

Referencing Values

You can substitute values from other settings by their declaration name with the following syntax:

BUILD_SETTING_NAME=$(ANOTHER_BUILD_SETTING_NAME)

Substitutions can be used to define new variables according to existing values, or inline to build up new values dynamically.

OBJROOT=$(SYMROOT)CONFIGURATION_BUILD_DIR=$(BUILD_DIR)/$(CONFIGURATION)-$(PLATFORM_NAME)

Setting Fallback Values for Referenced Build Settings

In Xcode 11.4 and later, you can use the default evaluation operator to specify a fallback value to use if the referenced build setting evaluates as empty.

$(BUILD_SETTING_NAME:default=value)

Conditionalizing Build Settings

You can conditionalize build settings according to their SDK (sdk), architecture (arch), and / or configuration (config) according to the following syntax:

BUILD_SETTING_NAME[sdk=sdk] =value for specified sdkBUILD_SETTING_NAME[arch=architecture] =value for specified architectureBUILD_SETTING_NAME[config=configuration] =value for specified configuration

Given a choice between multiple definitions of the same build setting, the compiler resolves according to specificity.

BUILD_SETTING_NAME[sdk=sdk][arch=architecture] =value for specified sdk and architecturesBUILD_SETTING_NAME[sdk=*][arch=architecture] =value for all other sdks with specified architecture

For example, you might specify the following build setting to speed up local builds by only compiling for the active architecture:

ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] =YES

Including Build Settings from Other Configuration Files

A build configuration file can include settings from other configuration files using the same #include syntax as the equivalent C directive on which this functionality is based:

#include "path/to/File.xcconfig"

As we’ll see later on in the article, you can take advantage of this to build up cascading lists of build settings in really powerful ways.

Creating Build Configuration Files

To create a build configuration file, select the “File > New File…” menu item (N), scroll down to the section labeled “Other”, and select the Configuration Settings File template. Next, save it somewhere in your project directory, making sure to add it to your desired targets

Xcode new configuration file

Once you’ve created an xcconfig file, you can assign it to one or more build configurations for its associated targets.

Xcode project configuration

Now that we’ve covered the basics of using Xcode build configuration files let’s look at a couple of examples of how you can use them to manage development, stage, and production environments.


Customizing App Name and Icon for Internal Builds

Developing an iOS app usually involves juggling various internal builds on your simulators and test devices (as well as the latest version from the App Store, to use as a reference).

You can make things easier on yourself with xcconfig files that assign each configuration a distinct name and app icon.

// Development.xcconfigPRODUCT_NAME=$(inherited)αASSETCATALOG_COMPILER_APPICON_NAME=AppIcon-Alpha//////////////////////////////////////////////////// Staging.xcconfigPRODUCT_NAME=$(inherited)βASSETCATALOG_COMPILER_APPICON_NAME=AppIcon-Beta

Managing Constants Across Different Environments

If your backend developers comport themselves according to the aforementioned12 Factor App philosophy, then they’ll have separate endpoints for development, stage, and production environments.

On iOS, perhaps the most common approach to managing these environments is to use conditional compilation statements with build settings like DEBUG.

importFoundation#if DEBUGletapiBaseURL=URL(string:"https://api.staging.example.com")!#elseletapiBaseURL=URL(string:"https://api.example.com")!#endif

This gets the job done, but runs afoul of the canon of code / configuration separation.

An alternative approach takes these environment-specific values and puts them where they belong — into xcconfig files.

// Development.xcconfigAPI_BASE_URL=api.staging.example.com//////////////////////////////////////////// Production.xcconfigAPI_BASE_URL=api.example.com

However, to pull these values programmatically, we’ll need to take one additional step:

Accessing Build Settings from Swift

Build settings defined by the Xcode project file, xcconfig files, and environment variables, are only available at build time. When you run the compiled app, none of that surrounding context is available.(And thank goodness for that!)

But wait a sec — don’t you remember seeing some of those build settings before in one of those other tabs? Info, was it?

As it so happens, that info tab is actually just a fancy presentation of the target’s Info.plist file. At build time, that Info.plist file is compiled according to the build settings provided and copied into the resulting app bundle. Therefore, by adding references to $(API_BASE_URL), you can access the values for those settings through the infoDictionary property of Foundation’s Bundle API.Neat!

Xcode Info.plist

Following this approach, we might do something like the following:

importFoundationenumConfiguration{enumError:Swift.Error{casemissingKey,invalidValue}staticfuncvalue<T>(forkey:String)throws->TwhereT:LosslessStringConvertible{guardletobject=Bundle.main.object(forInfoDictionaryKey:key)else{throwError.missingKey}switchobject{caseletvalueasT:returnvaluecaseletstringasString:guardletvalue=T(string)else{fallthrough}returnvaluedefault:throwError.invalidValue}}}enumAPI{staticvarbaseURL:URL{returntry!URL(string:"https://"+Configuration.value(for:"API_BASE_URL"))!}}

When viewed from the call site, we find that this approach harmonizes beautifully with our best practices — not a single hard-coded constant in sight!

leturl=URL(string:path,relativeTo:API.baseURL)!varrequest=URLRequest(url:url)request.httpMethod=method

Xcode projects are monolithic, fragile, and opaque. They’re a source of friction for collaboration among team members and generally a drag to work with.

Fortunately,xcconfig files go a long way to address these pain points. Moving configuration out of Xcode and into xcconfig files confers a multitude of benefits and offers a way to distance your project from the particulars of Xcode without leaving the Cupertino-approved “happy path”.


Swift Logging

$
0
0

In 2002, the United States Congress enacted the Sarbanes–Oxley Act, which introduced broad oversight to corporations in response to accounting scandals at companies likeEnron and MCI WorldCom around that time. This act,PCI and HIPAA, formed the regulatory backdrop for a new generation ofIT companies emerging from the dot-com bubble.

Around the same time, we saw the emergence of ephemeral, distributed infrastructure — what we now call “Cloud computing”— a paradigm that made systems more capable but also more complex.

To solve both the regulatory and logistical challenges of the 21st century, our field established best practices around application logging. And many of the same tools and standards are still in use today.


This week on NSHipster, we’re taking a look at SwiftLog: a community-driven, open-source standard for logging in Swift.

Developed by the Swift on Server community and endorsed by theSSWG (Swift Server Work Group), its benefit isn’t limited to use on the server. Indeed, any Swift code intended to be run from the command line would benefit from adopting SwiftLog. Read on to learn how.


As always, an example would be helpful in guiding our discussion. In the spirit of transparency and nostalgia, let’s imagine writing a Swift program that audits the finances of a ’00s Fortune 500 company.

importFoundationstructAuditor{funcwatch(_directory:URL)throws{}funccleanup(){}}do{letauditor=Auditor()defer{auditor.cleanup()}tryauditor.watch(directory:URL(string:"ftp:///reports")!,extensions:["xls","ods","qdf"])// poll for changes}catch{print("error: \(error)")}

An Auditor type polls for changes to a directory(an FTP server, because remember: it’s 2003). Each time a file is added, removed, or changed, its contents are audited for discrepancies. If any financial oddities are encountered, they’re logged using the print function. The same goes for issues connecting to the FTP, or any other problems the program might encounter — everything’s logged using print.

Simple enough. We can run it from the command line like so:

$swift run auditstarting up...
        ERROR: unable to reconnect to FTP#(try again after restarting PC under our desk)$swift run audit+ connected to FTP server
        ! accounting discrepancy in balance sheet 
        ** Quicken database corruption! **
        ^C
        shutting down...

Such a program might be technically compliant, but it leaves a lot of room for improvement:

  • For one, our output doesn’t have any timestamps associated with it. There’s no way to know whether a problem was detected an hour ago or last week.
  • Another problem is that our output lacks any coherent structure. At a glance, there’s no straightforward way to isolate program noise from real issues.
  • Finally, — and this is mostly due to an under-specified example— it’s unclear how this output is handled. Where is this output going? How is it collected, aggregated, and analyzed?

The good news is that all of these problems (and many others) can be solved by adopting a formal logging infrastructure in your project.


Adopting SwiftLog in Your Swift Program

Adding SwiftLog to an existing Swift package is a breeze. You can incorporate it incrementally without making any fundamental changes to your code and have it working in a matter of minutes.

Add swift-log as a Package Dependency

In your Package.swift manifest, add swift-log as a package dependency and add the Logging module to your target’s list of dependencies.

// swift-tools-version:5.1importPackageDescriptionletpackage=Package(name:"Auditor2000",products:[.executable(name:"audit",targets:["audit"])],dependencies:[.package(url:"https://github.com/apple/swift-log.git",from:"1.2.0"),],targets:[.target(name:"audit",dependencies:["Logging"])])

Create a Shared, Global Logger

Logger provides two initializers, the simpler of them taking a single label parameter:

letlogger=Logger(label:"com.NSHipster.Auditor2000")

In POSIX systems, programs operate on three, predefinedstreams:

File HandleDescriptionName
0stdinStandard Input
1stdoutStandard Output
2stderrStandard Error

By default,Logger uses the built-in StreamLogHandler type to write logged messages to standard output (stdout). We can override this behavior to instead write to standard error (stderr) by using the more complex initializer, which takes a factory parameter: a closure that takes a single String parameter (the label) and returns an object conforming to LogHandler.

letlogger=Logger(label:"com.NSHipster.Auditor2000",factory:StreamLogHandler.standardError)

Replacing Print Statements with Logging Statements

Declaring our logger as a top-level constant lets us call it anywhere within our module. Let’s revisit our example and spruce it up with our new logger:

do{letauditor=Auditor()defer{logger.trace("Shutting down")auditor.cleanup()}logger.trace("Starting up")tryauditor.watch(directory:URL(string:"ftp:///reports")!,extensions:["xls","ods","qdf"])// poll for changes}catch{logger.critical("\(error)")}

The trace, debug, and critical methods log a message at their respective log level.SwiftLog defines seven levels, ranked in ascending order of severity from trace to critical:

LevelDescription
.traceAppropriate for messages that contain information only when debugging a program.
.debugAppropriate for messages that contain information normally of use only when debugging a program.
.infoAppropriate for informational messages.
.noticeAppropriate for conditions that are not error conditions, but that may require special handling.
.warningAppropriate for messages that are not error conditions, but more severe than .notice
.errorAppropriate for error conditions.
.criticalAppropriate for critical error conditions that usually require immediate attention.

If we re-run our audit example with our new logging framework in place, we can see the immediate benefit of clearly-labeled, distinct severity levels in log lines:

$swift run audit2020-03-26T09:40:10-0700 critical: Couldn't connect to ftp://#(try again after plugging in loose ethernet cord)$swift run audit2020-03-26T10:21:22-0700 warning: Discrepancy in balance sheet
        2020-03-26T10:21:22-0700 error: Quicken database corruption
        ^C

Beyond merely labeling messages,which — don’t get us wrong — is sufficient benefit on its own, log levels provide a configurable level of disclosure. Notice that the messages logged with the trace method don’t appear in the example output. That’s because Logger defaults to showing only messages logged as info level or higher.

You can configure that by setting the Logger’s logLevel property.

varlogger=Logger(label:"com.NSHipster.Auditor2000")logger.logLevel=.trace

After making this change, the example output would instead look something like this:

$swift run audit2020-03-25T09:40:00-0700 trace: Starting up
        2020-03-26T09:40:10-0700 critical: Couldn't connect to ftp://
        2020-03-25T09:40:11-0700 trace: Shutting down#(try again after plugging in loose ethernet cord)$swift run audit2020-03-25T09:41:00-0700 trace: Starting up
        2020-03-26T09:41:01-0700 debug: Connected to ftp:///reports
        2020-03-26T09:41:01-0700 debug: Watching file extensions ["xls", "ods", "qdf"]
        2020-03-26T10:21:22-0700 warning: Discrepancy in balance sheet
        2020-03-26T10:21:22-0700 error: Quicken database corruption
        ^C
        2020-03-26T10:30:00-0700 trace: Shutting down

Using Multiple Logging Handlers at Once

Thinking back to our objections in the original example, the only remaining concern is what we actually do with these logs.

According to 12 Factor App principles:

XI. Logs

[…]

A twelve-factor app never concerns itself with routing or storage of its output stream. It should not attempt to write to or manage logfiles. Instead, each running process writes its event stream, unbuffered, to stdout.

Collecting, routing, indexing, and analyzing logs across a distributed system often requires a constellation of open-source libraries and commercial products. Fortunately, most of these components traffic in a shared currency ofsyslog messages — and thanks to this package by Ian Partridge, Swift can, as well.

That said, few engineers have managed to retrieve this information from the likes of Splunk and lived to tell the tale. For us mere mortals, we might preferthis package by Will Lisac, which sends log messages toSlack.

The good news is that we can use both at once, without changing how messages are logged at the call site by using another piece of the Logging module:MultiplexLogHandler.

importstructFoundation.ProcessInfoimportLoggingimportLoggingSyslogimportLoggingSlackLoggingSystem.bootstrap{labelinletwebhookURL=URL(string:ProcessInfo.processInfo.environment["SLACK_LOGGING_WEBHOOK_URL"]!)!varslackHandler=SlackLogHandler(label:label,webhookURL:webhookURL)slackHandler.logLevel=.criticalletsyslogHandler=SyslogLogHandler(label:label)returnMultiplexLogHandler([syslogHandler,slackHandler])}letlogger=Logger(label:"com.NSHipster.Auditor2000")

With all of this in place, our system will log everything in syslog format to standard out (stdout), where it can be collected and analyzed by some other system.


But the real strength of this approach to logging is that it can be extended to meet the specific needs of any environment. Instead of writing syslog to stdout or Slack messages, your system could send emails, open SalesForce tickets, or trigger a webhook to activate some IoT device.

Here’s how you can extend SwiftLog to fit your needs by writing a custom log handler:

Creating a Custom Log Handler

The LogHandler protocol specifies the requirements for types that can be registered as message handlers by Logger:

protocolLogHandler{subscript(metadataKey_:String)->Logger.Metadata.Value?{getset}varmetadata:Logger.Metadata{getset}varlogLevel:Logger.Level{getset}funclog(level:Logger.Level,message:Logger.Message,metadata:Logger.Metadata?,file:String,function:String,line:UInt)}

In the process of writing this article, I created custom handler that formats log messages for GitHub Actions so that they’re surfaced on GitHub’s UI like so:

If you’re interested in making your own logging handler, you can learn a lot by just browsing the code for this project. But I did want to call out a few points of interest here:

Conditional Boostrapping

When bootstrapping your logging system, you can define some logic for how things are configured. For logging formatters specific to a particular CI vendor, for example, you might check the environment to see if you’re running locally or on CI and adjust accordingly.

importLoggingimportLoggingGitHubActionsimportstructFoundation.ProcessInfoLoggingSystem.bootstrap{labelin// Are we running in a GitHub Actions workflow?ifProcessInfo.processInfo.environment["GITHUB_ACTIONS"]=="true"{returnGitHubActionsLogHandler.standardOutput(label:label)}else{returnStreamLogHandler.standardOutput(label:label)}}

Testing Custom Log Handlers

Testing turned out to be more of a challenge than originally anticipated. I could be missing something obvious, but there doesn’t seem to be a way to create assertions about text written to standard output. So here’s what I did instead:

First, create an internal initializer that takes a TextOutputStream parameter, and store it in a private property.

publicstructGitHubActionsLogHandler:LogHandler{privatevaroutputStream:TextOutputStreaminternalinit(outputStream:TextOutputStream){self.outputStream=outputStream}}

Then, in the test target, create a type that adopts TextOutputStream and collects logged messages to a stored property for later inspection. By using a @testable import of the module declaring GitHubActionsLogHandler, we can access that internal initializer from before, and pass an instance of MockTextOutputStream to intercept logged messages.

importLogging@testableimportLoggingGitHubActionsfinalclassMockTextOutputStream:TextOutputStream{publicprivate(set)varlines:[String]=[]publicinit(_body:(Logger)->Void){letlogger=Logger(label:#file){labelinGitHubActionsLogHandler(outputStream:self)}body(logger)}// MARK: - TextOutputStreamfuncwrite(_string:String){lines.append(string)}}

With these pieces in place, we can finally test that our handler works as expected:

functestLogging(){varlogLevel:Logger.Level?letexpectation=MockTextOutputStream{loggerinlogLevel=logger.handler.logLevellogger.trace("🥱")logger.error("😱")}XCTAssertGreaterThan(logLevel!,.trace)XCTAssertEqual(expectation.lines.count,1)// trace log is ignoredXCTAssertTrue(expectation.lines[0].hasPrefix("::error "))XCTAssertTrue(expectation.lines[0].hasSuffix("::😱"))}

Cross-Pollination

$
0
0

April is the month when apple trees start to bloom up here in the Pacific Northwest. All across Oregon’s Willamette Valley, from Portland stretching south to Eugene, long-barren branches sprout white, 5-petaled blossoms tinged with pink. Any other year, our family would be taking weekend trips southwest to Sherwood or east towards Hood River to visit their orchards.

Like the Fuji and Gala varieties that predominate in this region, most apple cultivars are self-unfruitful— which is to say that they require cross-pollination to produce a good crop consistently.

When fertilized by the pollen of Fuji apple blossoms (or those of Braeburn, Honey Crisp, or McIntosh varieties), a Gala apple tree can yield 20 kilograms of fruit each season. Those Gala trees, in return, endow their pollen on the Fuji apple trees so that they too may blossom and bear one or two bushels of fruit, each.

The Dance of the Honey Bee

Appletree pollen is sticky. In contrast with the windborne pollen of Alder, Birch, and Ash trees (whose allergenic quality gave the Willamette its name, meaning “valley of sickness” in the indigenous Kalapuya dialect), appletrees rely on insects to transfer pollen — particularly the honey bee.

Honey bees eat the pollen of flowers and convert their nectar into honey. Some of the pollen sticks to their furry bodies, which is inadvertently spread as they move from plant to plant.

When a scout bee encounters a food source, she flies back to the hive and communicates the location of that food source to male worker bees by performing what’s called a waggle dance. Performed in darkness on the vertical honeycomb surface in the hive, she’s able to convey the precise location of new food sources to them by flying a coffee bean-shaped pattern oriented in the direction of the sun. It’s an incredible feat, made all the more remarkable by the fact that bees are not, individually, very intelligent. Bees have brains on the order of 1 million neurons, compared to the 100 billion neurons of a human brain.

If you move a food source closer and farther away from a hive, you can see how the dance changes to convey this new information. But move it just past some critical point, and the dance becomes something entirely different: instead of the waggle dance, the bee performs a round dance with a totally different cadence and flight path.

For many years, the dance language of the bumblebee eluded all those who studied it. That is until a mathematician named Barbara Shipman made the connection between a bee’s dance language and the six-dimensional geometry of flag manifolds, of all things. What was the unique insight that allowed her to see what others couldn’t? She grew up in a family of beekeepers and cultivated an interest in mathematics and biology that carried throughout her studies.

The leap from furry, buzzing insects to abstract geometry is inconceivable unless you’re accustomed to looking at the world in that particular way.

The Rose that Grows From the Dunghill

When Apple first announced the Swift programming language in 2014, it generated a flurry of excitement as we all tried to understand it. One of the most powerful tools at our disposal for understanding is analogy:

New Thing is like Familiar Thing crossed with Another Thing.

So in those early days, there was a lot of discussion within the community attempting to compare and contrast Swift withHaskell or Go or Python or Scheme or Dylan.

Last year, we saw something similar with at WWDC 2019. Anyone familiar with React or Elm immediately recognized their influence onSwiftUI and Combine (even if Apple hadn’t come out and acknowledged it explicitly).

For some, the connection between React and Elm with JavaScript is an inconvenient truth. I’ve seen numerous developers profess their disdain for the language in ways that echo the old rivalry between iOS and Android (or the even older rivalry between Mac and PC).

And yet, there are countless examples of good ideas from “them” being criticized and mocked until they’re incorporated into an Apple product:

All of which begs the question:

Why did we consider these good ideas heretical until Apple did it?

Us vs. Them

Another flavor of this arises from the dichotomy between “Native” and “Hybrid”.

Whenever a company writes some blog post about React Native, what inevitably follows is chorus of developers who either praise the decision as courageous (if switching away) or call it idiotic (if adopting it).

As developers, we tend to align ourselves with enlightenment ideals like objectivity. We say that we make decisions based in the indisputable reality of fact. We consider ourselves reasonable and our decisions well-reasoned.

But to what extent is this actually true? Do our thoughts lead us to our preferences, or do we use thoughts to rationalize them after the fact?


In the 1960s and 70s, the social psychologist Henri Tajfel and his colleagues ran a series of experiments that demonstrated how little it takes for people to engage in intergroup discrimination.

In one experiment, a group of boys were shown pictures with clusters of dots and instructed to guess how many there were as a test of their visual judgment. The researchers split the group between those who overestimated or underestimated the number. Except, they only pretended to do this — the boys were, in fact, randomly assigned to one of the two groups. They were then given the task of allocating a fixed amount of real money to other boys in the study.

The results surprised even the researchers:

Overwhelmingly, the boys chose outcomes where their assigned group (under- or over-estimators) received more money than their counterparts — even when that meant getting less overall.

Successful replication of these results in follow-up studies since then presents compelling evidence of this peculiarity in human nature. That a willingness to engage in “us vs. them” discrimination can arise from completely artificial distinctions, irrespective of any rationale of self-interest.


How else could you explain the intense tribalism around how we talk to computers?

The Dream of Purity

When a developer proudly declares something to be“Pure Swift” or “100% JavaScript free”, what are they really saying? What’s presented as an objective statement of fact often feels more like an oath of allegiance.

If you see the existence of competing technologies as a fight between good and evil, perhaps there are more important battles to fight. If you can’t evaluate solutions as a series of trade-offs, what chance do you have at accomplishing anything at all?

Yes, there are real differences between technologies and reasonable people disagree about which one is best-suited to solve a particular problem. But don’t mistake this for a moral contest.

Purity is an ideal; a vision of the condition which needs yet to be created, or such as needs to be diligently protected against the genuine or imagined odds. Without such a vision, neither the concept of purity makes sense, nor the distinction between purity and impurity can be sensibly drawn.

– Zygmunt Bauman


It’s of no practical consequence that the grounds on which Apple Park sits today were fruit orchards a hundred years ago. But it’s poetic. Long before it was “Silicon Valley”, the stretch of land between the San Andreas and Hayward faults was called “the Valley of Heart’s Delight” for all of its fruit trees and flowering plants.

Dwelling on this, you might reflect on how humans are like apple trees. That we need a variety of different influences to reach our potential. (Even self-starters benefit from a unique perspective).

You might then consider what we share in common with the bees that pollinate apple trees. Like them, our success comes not from our individual intelligence, but in our ability to share information.

Whether we’re like bees or like apples, we come away learning the same lesson: We can achieve remarkable results by working together.

Contact Tracing

$
0
0

An ounce of prevention is worth a pound of cure.

Early intervention is among the most effective strategies for treating illnesses. This is true not only for the human body, for society as a whole. That’s why public health officials use contact tracing as their first line of defense against the spread of infectious disease in a population.

We’re hearing a lot about contact tracing these days, but the technique has been used for decades. What’s changed is that thanks to the ubiquity of personal electronic devices, we can automate what was — up until now — a labor-intensive, manual process. Much like how “computer” used to be a job title held by humans, the role of “contact tracer” may soon be filled primarily by apps.

On April 10th, Apple and Google announced a joint initiative to deploy contact tracing functionality to the billions of devices running iOS or Android in the coming months. As part of this announcement, the companies shared draft specifications for thecryptography,hardware, andsoftware involved in their proposed solution.

In this article, we’ll take a first look at these specifications — particularly Apple’s proposed ExposureNotification framework — and use what we’ve learned to anticipate what this will all look like in practice.


What is contact tracing?

Contact tracing is a technique used by public health officials to identify people who are exposed to an infectious disease in order to slow the spread of that illness within a population.

When a patient is admitted to a hospital and diagnosed with a new, communicable disease, they’re interviewed by health workers to learn who they’ve interacted recently. Any contacts whose interactions with the patient are then evaluated, and if they’re diagnosed with the disease, the process repeats with their known, recent contacts.

Contact tracing disrupts the chain of transmission. It gives people the opportunity to isolate themselves before infecting others and to seek treatment before they present symptoms. It also allows decision-makers to make more informed recommendations and policy decisions about additional measures to take.

If you start early and act quickly, contact tracing gives you a fighting chance of containing an outbreak before it gets out of hand.

Unfortunately, we weren’t so lucky this time around.

With over a million confirmed cases of COVID-19 worldwide, many regions are well past the point where contact tracing is practical. But that’s not to say that it can’t play an essential role in the coming weeks and months.

“Only Apple (and Google) can do this.”

Since the outbreak, various governments and academics have proposed standards for contact tracing. But the most significant development so far came yesterday with Apple and Google’s announcement of a joint initiative.

According to theNHS, around 60% of adults in a population would need to participate in order for digital contact tracing to be effective. Researchers from the aforementioned institutions have noted that the limits imposed by iOS on 3rd-party apps make this level of participation unlikely.

On the one hand, it feels weird to congratulate Apple for stepping in to solve a problem it created in the first place. But we can all agree that this announcement is something to celebrate. It’s no exaggeration to say that this wouldn’t be possible without their help.

What are Apple and Google proposing as a solution?

At a high level, Apple and Google are proposing a common standard for how personal electronic devices (phones, tablets, watches) can automate the process of contact tracing.

Instead of health workers chasing down contacts on the phone — a process that can take hours, or even days — the proposed system could identify every recent contact and notify all of them within moments of a confirmed, positive diagnosis.

Apple’s CEO, Tim Cook, promises that “Contact tracing can help slow the spread of COVID-19 and can be done without compromising user privacy.”. The specifications accompanying the announcement show how that’s possible.

Let’s take them in turn, starting with cryptography (key derivation & rotation), followed byhardware (Bluetooth), andsoftware (app) components.

Cryptography

When you install an app and open it for the first time, the Exposure Notification framework displays a dialog requesting permission to enable contact tracing on the device.

If the user accepts, the framework generates a 32-byte cryptographic random number to serve as the device’s Tracing Key. The Tracing Key is kept secret, never leaving the device.

Every 24 hours, the device takes the Tracing Key and the day number (0, 1, 2, …) and uses HKDF to derive a 16-byte Daily Tracing KeyTemporary Exposure Key. These keys stay on the device, unless you consent to share them.

Every 15 minutes, the device takes the Temporary Exposure Key and the number of 10-minute intervals since the beginning of the day (0 – 143), and uses HMAC to generate a new 16-byte Rolling Proximity Identifier. This identifier is broadcast from the device usingBluetooth LE.

If someone using a contact tracing app gets a positive diagnosis, the central health authority requests their Temporary Exposure Keys for the period of time that they were contagious. If the patient consents, those keys are then added to the health authority’s database as Positive Diagnosis Keys. Those keys are shared with other devices to determine if they’ve had any contact over that time period.

Hardware

Bluetooth organizes communications between devices around the concept of services.

A service describes a set of characteristics for accomplishing a particular task. A device may communicate with multiple services in the course of its operation. Many service definitions are standardized so that devices that do the same kinds of things communicate in the same way.

For example, a wireless heart rate monitor that uses Bluetooth to communicate to your phone would have a profile containing two services: a primary Heart Rate service and a secondary Battery service.

Apple and Google’s Contact Tracing standard defines a new Contact Detection service.

When a contact tracing app is running (either in the foreground or background), it acts as a peripheral, advertising its support for the Contact Detection service to any other device within range. The Rolling Proximity Identifier generated every 15 minutes is sent in the advertising packet along with the 16-bit service UUID.

Here’s some code for doing this from an iOS device using the Core Bluetooth framework:

importCoreBluetooth// Contact Detection service UUIDletserviceUUID=CBUUID(string:"FD6F")// Rolling Proximity Identifierletidentifier:Data=// 16 bytesletperipheralManager=CBPeripheralManager()letadvertisementData:[String:Any]=[CBAdvertisementDataServiceUUIDsKey:[serviceUUID]CBAdvertisementDataServiceDataKey:identifier]peripheralManager.startAdvertising(advertisementData)

At the same time that the device broadcasts as a peripheral, it’s also scanning for other devices’ Rolling Proximity Identifiers. Again, here’s how you might do that on iOS using Core Bluetooth:

letdelegate:CBCentralManagerDelegate=letcentralManager=CBCentralManager(delegate:delegate,queue:.main)centralManager.scanForPeripherals(withServices:[serviceUUID],options:[:])extensionDelegateClass:CBCentralManagerDelegate{funccentralManager(_central:CBCentralManager,didDiscoverperipheral:CBPeripheral,advertisementData:[String:Any],rssiRSSI:NSNumber){letidentifier=advertisementData[CBAdvertisementDataServiceDataKey]as!Data}}

Bluetooth is an almost ideal technology for contact tracing. It’s on every consumer smart phone. It operates with low power requirement, which lets it run continuously without draining your battery. And it just so happens to have a transmission range that approximates the physical proximity required for the airborne transmission of infectious disease. This last quality is what allows contact tracing to be done without resorting to location data.

Software

Your device stores any Rolling Proximity Identifiers it discovers, and periodically checks them against a list of Positive Diagnosis Keys sent from the central health authority.

Each Positive Diagnosis Key corresponds to someone else’s Temporary Exposure Key. We can derive all of the possible Rolling Proximity Identifiers that it could advertise over the course of that day (using the same HMAC algorithm that we used to derive our own Rolling Proximity Identifiers). If any matches were found among your device’s list of Rolling Proximity Identifiers, it means that you may have been in contact with an infected individual.

Suffice to say that digital contact tracing is really hard to get right. Given the importance of getting it right, both in terms of yielding accurate results and preserving privacy, Apple and Google are providing SDKs for app developers to use for iOS and Android, respectively.

All of the details we discussed about cryptography and Bluetooth are managed by the framework. The only thing we need to do as developers is communicate with the user — specifically, requesting their permission to start contact tracing and notifying them about a positive diagnosis.

ExposureNotification

When Apple announced the ContactTracing framework on April 10th, all we had to go on were some annotated Objective-C headers. But as of the first public beta of iOS 13.5, we now have official documentation under its name: ExposureNotification.

Calculating Risk of Exposure

A contact tracing app regularly fetches new Positive Diagnosis Keys from the central health authority. It then checks those keys against the device’s Rolling Proximity Identifiers. Any matches would indicate a possible risk of exposure.

In the first version of ContactTracing, all you could learn about a positive match was how long you were exposed (in 5 minute increments) and when contact occurred (with an unspecified level of precision). While we might applaud the level of privacy protections here, that doesn’t offer much in the way of actionable information. Depending on the individual, a push notification saying “You were in exposed for 5–10 minutes sometime 3 days ago” could warrant a visit to the hospital or elicit no more concern than a missed call.

With ExposureNotification, you get a lot more information, including:

  • Days since last exposure incident
  • Cumulative duration of the exposure (capped at 30 minutes)
  • Minimum Bluetooth signal strength attenuation(Transmission Power - RSSI), which can tell you how close they got
  • Transmission risk, which is an app-definied value that may be based on symptoms, level of diagnosis verification, or other determination from the app or a health authority

For each instance of exposure, an ENExposureInfo object provides all of the aforementioned information plus an overall risk score(from 1 to 8), which is calculated fromthe app’s assigned weights for each factor, according to this equation:

Apple provides this example in their framework documentation PDF:

Managing Permissions and Disclosures

The biggest challenge we found with the original Contact Tracing framework API was dealing with all of its completion handlers. Most of the functionality was provided through asynchronous APIs; without a way to compose these operations, you can easily find yourself nested 4 or 5 closures deep, indented to the far side of your editor.

Fortunately, the latest release of Exposure Notification includes a newENManager class, which simplifies much of that asynchronous state management.

letmanager=ENManager()manager.activate{erroringuarderror==nilelse{}manager.setExposureNotificationEnabled(true){erroringuarderror==nilelse{}// app is now advertising and monitoring for tracing identifiers}}

Tracing a path back to normal life

Many of us have been sheltering in place for weeks, if not months. Until a vaccine is developed and made widely available, this is the most effective strategy we have for stopping the spread of the disease.

But experts are saying that a vaccine could be anywhere from 9 to 18 months away.“What will we do until then?”

At least here in the United States, we don’t yet have a national plan for getting back to normal, so it’s hard to say. What we do know is that it’s not going to be easy, and it’s not going to come all at once.

Once the rate of new infections stabilizes, our focus will become containing new outbreaks in communities. And to that end, technology-backed contact tracing can play a crucial role.

From a technical perspective, Apple and Google’s proposal gives us every reason to believe that we can do contact tracing without compromising privacy. However, the amount of faith you put into this solution depends on how much you trust these companies and our governments in the first place.

Personally, I remain cautiously optimistic. Apple’s commitment to privacy has long been one of its greatest assets, and it’s now more important than ever.

Swift Development with Visual Studio Code

$
0
0

Visual Studio Code (VSCode) is a cross-platform text and source code editor from Microsoft. It’s one of the most exciting open source projects today, with regular updates from hundreds of contributors. VSCode was among the first tools to support Language Server Protocol (LSP), which has played a large part in providing a great developer experience, in a variety of languages and technologies.

With the previously announcednow shipping in Xcode, it’s a great time to see how this integration works for yourself.

This week, we’ll walk through the process of how to get started with Swift’s new Language Server Protocol support in Visual Studio Code on macOS. If you haven’t tried writing Swift outside Xcode, or are already a VSCode user and new to the language entirely, this article will tell you everything you need to know.


Step 0: Install Xcode

If you don’t already have Xcode installed on your machine, open the Terminal app and run the following command:

$xcode-select --install

Running this command presents a system prompt.

Click the “Get Xcode” button and continue installation on the App Store.

You can verify that everything is working as expected by running the sourcekit-lsp command:

$xcrun sourcekit-lsp

This command launches a new language server process, but don’t worry if it doesn’t provide any feedback to STDOUT— that means it’s working as intended. Exit the process with an ETX signal (^C).

Step 1: Install Visual Studio Code

Download Visual Studio Code and install it to your system Applications folder. Open the app andfollow the instructions for launching from the command line. You’ll need to have the code command accessible from $PATH in order to install the SourceKit-LSP extension later on.

Step 2: Install Node and NPM

VSCode extensions are written in JavaScript / TypeScript. If you’re not already set up for JS development, you can download Node (a JavaScript run-time for outside the browser)
and npm (a package manager for Node) with Homebrew using the following commands or manually by following these instructions:

$brew install node

To verify that you have a working installation, run the following command:

$npm --version6.13.4

Step 3: Build and Install SourceKit-LSP Extension for Visual Studio Code

From the command line, clone the sourcekit-lsp repository and navigate to Editors/vscode in the resulting directory. Use npm to build the extension and then use the code command to install it:

$git clone https://github.com/apple/sourcekit-lsp.git$cd sourcekit-lsp/Editors/vscode/$npm run createDevPackage$code --install-extension out/sourcekit-lsp-vscode-dev.vsix

Now launch (or relaunch) VSCode and open a Swift project, such as this one, and test out Language Server Protocol support for Swift.


So there you have it — the makings of a first-class Swift development experience outside of Xcode.

And with GitHub’s recent announcement of Codespaces, that future may be coming sooner than we once thought. Thanks to Swift’s support for Language Server Protocol, we’ll soon be able to edit Swift code — syntax highlighting, autocompletion, and all — directly from the browser.

Language Server Protocol

$
0
0

In October 2018, Apple announced on the Swift.org forums that it was starting work to adopt the Language Server Protocol (LSP) for Swift and C languages.

At Apple we are making it a priority to support high-quality tooling for all Swift developers, including those working on non-Apple platforms. We want to collaborate with the open-source community and focus our efforts on building common infrastructure that can be shared by Xcode and other editors and platforms. To that end, [ … ] we’ve chosen to adopt LSP.

Argyrios Kyrtzidis, October 15th, 2018

This is arguably the most important decision Apple has made for Swift since releasing the language as open source in 2014. It’s a big deal for app developers, and it’s an even bigger deal for Swift developers on other platforms.

To understand why, this week’s article will take a look at what problem the Language Server Protocol solves, how it works, and what its long-term impacts may be.


Imagine a grid with each row representing a different programming language (Swift, JavaScript, Ruby, Python, etc.) and each column representing a different code editor (Xcode, Visual Studio, Vim, Atom, etc.), such that each cell represents the level of support that a particular editor has for a language.

Up until recently, what you’d find was a patchwork of compatibility across the various combinations. Some editors offered deep integration with a few languages and little to no support for anything else, whereas other editors aimed to be general-purpose with at least a modicum of support for many languages. (The term IDE is often used to describe the former.)

Case in point:You’d be stubborn not to use Xcode for app development and foolish to use it for anything else.

For an editor to have better support for a particular language, it needs to write integration code — either directly in the code base or via a plugin system. Due to implementation differences across languages and editors, improvements to, say, Ruby support in Vim wouldn’t translate into better support for Python, nor could they be applied to make Ruby work better in Atom. The end result: inconsistent support across technologies and a lot of wasted effort.

The situation we described is often referred to as an M × N problem, where the number of integrations is the product ofM editors and N languages. What the Language Server Protocol does is change this M × N problem into a M + N problem.

Rather than an editor having to implement support for each language, it only needs to support the LSP. And in doing so, it gets the same level of functionality for all languages that support the LSP.

Language Server Protocol provides a common set of functionality for supported languages, including:

  • Syntax Highlighting
  • Automatic Formatting
  • Autocomplete
  • Syntax
  • Tooltips
  • Inline Diagnostics
  • Jump to Definition
  • Find References in Project
  • Advanced Text and Symbol Search

Rather than reinventing the wheel for each new technology, tools and editors can invest in better usability and more advanced functionality.

How Language Server Protocol Works

If you’re an iOS developer, you may be most familiar with the terms server and protocol in the sense of communicating with web applications in JSON format via HTTP. This actually isn’t too far off from how the Language Server Protocol works.

In the case of LSP, the client refers to the editor — or more generally, the tool — and the server refers to an external program run locally in a separate process.

As for the protocol itself, LSP resembles a simplified version of HTTP:

  • Each message consists of a header part and a content part.
  • The header part has a required Content-Length field containing the size of the content part in bytes, and an optional Content-Type field (application/vscode-jsonrpc; charset=utf-8 by default)
  • The content part uses JSON-RPC to describe the structure of requests, responses, and notifications.

Whenever something happens in the tool, such as the user jumping to the definition of a symbol, the tool sends a request to the server. The server receives that request and then returns an appropriate response.

For example, imagine that a user opens the following Swift code in an Xcode-like editor that supported the Language Server Protocol:

classParent{}classChild:Parent{}

When the user -clicks the symbol Parent in the inheritance clause on line 2, the editor jumps to the definition of the Parent class on line 1.

Here’s how LSP enables this interaction behind the scenes:

First, when the user opens the Swift code, the editor launches its Swift language server in a separate process, if it isn’t running already, and performs any additional setup.

When the user executes the “jump to definition” command, the editor sends the following request to its Swift language server:

{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///Users/NSHipster/Example.swift"},"position":{"line":1,"character":13}}}

Upon receiving this request, the Swift language server uses a compiler tool likeSourceKit to identify the corresponding code entity and find the location of its declaration on the preceding line. The language server then responds with the following message:

{"jsonrpc":"2.0","id":1,"result":{"uri":"file:///Users/NSHipster/Example.swift","range":{"start":{"line":0,"character":6},"end":{"line":0,"character":12}}}}

Finally, the editor navigates to the file (which, in this case, is already open), moves the cursor to that range, and highlights the token.

The beauty of this approach is that the editor did all of this without knowing anything about the Swift programming language other than that .swift files are associated with Swift code. All the editor needs to do is talk to the language server and update the UI. And knowing how to do that, the editor can follow the same procedure to facilitate this interaction for code written in any language with a language server implementation.

Language Server Protocol Support in Clang / LLVM

If the M + N diagram from before looks familiar, it might be because it’s the same approach taken by LLVM.

At the core of LLVM is an intermediate representation (IR). Supported languages generate IR using a compiler frontend, and that IR can generate machine code for any platform supported by a compiler backend.

The LLVM compiler frontend for C languages is called Clang. It’s also used by Swift for inter-operability with Objective-C. In its recent 5.0.0 release, Clang added a new tool called Clangd, LLVM’s implementation for the Language Server Protocol.

In April 2018,Apple announced to the LLVM mailing list that it was switching the focus of its development efforts fromlibclang to Clangd as the primary way to create interactive tooling.

Now you might think, “So what?” Apple is among the most prominent supporters of the LLVM project — having, among other things, employed the project’s founder, Chris Lattner, for over a decade. Apple’s decision to switch from one obscure Clang tool to another would seem to be an implementation detail (so to speak).

What makes this announcement quite interesting is that Clangd appears to have been created entirely outside of Apple, with significant contributions from Google and other companies. This announcement signals a significant shift in the direction of tooling development going forward — something that would be confirmed 6 months later on the Swift.org forums.

Getting Started with Language Server Protocol

Xcode 11.4 includes sourcekit-lsp in its default toolchain. You can use the xcrun command to get the path to the language server executable:

$xcrun -f sourcekit-lsp/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp

Check out our article about Visual Studio Code to get started with our go-to editors. Beyond that, the sourcekit-lsp project on GitHub hasinstructions for integrating with Sublime Text, Vim, Emacs, and others..

Potential Consequences of Apple’s Support of Language Server Protocol

It’ll take some time to feel the full impact of these developments, but believe me: your patience will be rewarded. Here are just a few of what I believe will happen as a result of LSP in the coming months and years.

Swift Becomes More Appealing as a General-Purpose Programming Language

Although Swift is used primarily for app development, it was designed from the start to be a capable general-purpose programming language. BetweenSwift for TensorFlow,SwiftNIO, and other projects, we’re just starting to see the promise of what Swift can do beyond the App Store.

Among the biggest factors holding Swift back from mainstream adoption up to this point has been its reliance on Xcode.

It’s a lot to ask, say, a web developer or machine learning engineer to download Xcode just to try Swift when there are so many great alternatives with a much lower barrier to entry. Support for the Language Server Protocol should make it significantly easier for folks outside the Apple ecosystem to evaluate Swift with the same, familiar tools they use for everything else.

Xcode Gets Better

Adopting LSP isn’t just about making Swift work better in other editors; Xcode stands to benefit immensely, as well.

Consider this forum post from Project Lead for Swift at Apple, Ted Kremenek:

The LSP service [Argyrios] is describing will be functionally more powerful than SourceKit is today.

LSP is an opportunity for the Xcode team to take a fresh approach to Swift integration, and to capitalize on all of the improvements to the language and tooling in the four years since its 1.0 release.

Our first glimpse into this overhauled infrastructure comes by way ofIndexStoreDB: a powerful new API for querying code symbols in Swift projects from a Clang index.

Xcode (Eventually) Becomes More Capable

The benefit of LSP isn’t limited to Swift and Objective-C; from another post by Argyrios in that thread:

Getting Xcode to use our new LSP service should make it viable to use other LSP services as well, and it’s something that we are interested in, but we don’t have specific plans to announce at this moment.

The main focus for the current efforts are to improve the story for Swift. But once that’s done, it should be relatively straightforward to have those benefits cascade down to other languages with LSP support.


The architecture of software reflects the structure and values of the organizations that create it. The converse is true as well, to some extent.

By adopting the open Language Server Protocol standard for Xcode, Apple is making good on its commitment to the success of Swift on platforms outside the Apple ecosystem. And I think it’ll work: tooling (or lack thereof) is often the key decider in which technologies gain mindshare. But perhaps more importantly, I believe this decision demonstrates an increased willingness within (at least some small part of) the company for collaboration and transparency.

WWDC 2020

$
0
0

Like everything else in 2020, this year’s WWDC had to be a little different if it was going to happen at all.

When Apple first announced that the conference would be fully remote, nobody knew what that would look like, exactly. What parts of the dubdub experience would be kept in this new format? What details would be lost in translation? Could they actually pull it off?

As it turns out, going fully remote wasn’t merely good enough — it was, in many ways, superior to the original thing. There’s a lot to like about the new format.

The videos are well-produced, and let each presenter’s personality really shine. Everybody looks and sounds great.

Sessions are tight and well-paced. Rather than stretching or cramming content into a fixed time slot, they’re as long as they need to be. And thanks to this more digestible format, we’re starting to see WWDC clips being shared around, which is new and refreshing.

To be honest, it’s hard to imagine ever going back to a physical conference.

However, as someone who had the privilege of attending WWDC in years past, there are things I’m going to miss (and some that I decidedly won’t).

🥰😫
Refrigerators stocked with Odwalla smoothiesTrying to download the latest Xcode beta over hotel internet
Lunchtime sessionsEating lunch at or around Moscone
WWDC track jackets saving the lives of first-time attendees from the cold of San Francisco summerBeing in San Francisco, generally
Eating burritos on the terrace of McEnery Convention Center during WWDC check-inBeing in San Jose, generally
Guessing who would be playing at Bash this yearHearing the same handful of songs on repeat before and after every session (this song in particular)
Watching Apple executives dance at Bash
Talking to people as you wait in line for the keynote on Monday morningWaking up late and having to settle for watching from the overflow room
Leaving at the end of the week with a mix of hope, fear, and inspiration *Being sick for the next week with dubdub flu

* I’d like to hold on this last point for a moment.


In the Before Times, many of us traveled far from home to attend WWDC. There was a physical and temporal delineation between life before, during, and after the conference. Flying out of SFO, SJC, or OAK, you escaped Apple’s “reality distortion field” and its surrounding echo chamber. You returned to your normal life.

This year? Not so much.

WWDC 2020 was just another week in this bizarre existence amidst this pandemic. Not only is there no escape from the “reality distortion field”, there isn’t even a “normal life” for us to leave or return to.

So here we are, filled with anxious excitement and fear; our corporeal forms replaced by Memoji floating in a black, digital void lit only by the screens of our soon-to-be-obsolete MacBooks Pro.


Excitement

I don’t have a real sense of how everything went over this year. There wasn’t any applause (or heckling) at this year’s keynote to gauge the temperature in the room. There were no parties to overhear shop talk and hot takes. There was no line for lunch to make small talk with a fellow attendee.

But if Twitter is anything to go on, my overall impression is that everyone is really excited.

Which is fine. I get it.

But it should come as no surprise that things announced at WWDC are exciting — that’s the whole point of having a developer conference in the first place. Apple has the best marketing in the world, and WWDC is Apple’s way of marketing to us.

Here’s the thing about excitement: It’s kryptonite to developers.

Excitement messes with our ability to focus on one thing, which is already a big struggle for a lot of us (myself included). When you’re excited, it’s almost impossible to get anything done.

There are plenty of voices in the community who are echoing this excitement. I can’t add anything to that discussion. And besides, that’s not really where my head’s at right now.

Trivial Pursuit

I briefly considered reviving the NSHipster Quiz for WWDC 2020, but it didn’t feel right. With everything going on in the world, Apple trivia just isn’t where my head or heart are right now.

To give you a sense of what I mean, here’s what I had for Round 3, whose theme was inspired by Richard Hamming:

Question 1.
What are the important problems of your field?
Question 2.
What important problems are you working on?
Question 3.
If what you are doing is not important, why are working on it?

Temperance

If you’re serious about solving a problem, you owe it to yourself to temper any excitement that distracts you from making real progress.

In last year’s write-up for WWDC 2019, I concluded with the following, as a counterpoint to the conference’s theme of #mindblown🤯:

Taking care of yourself — sleeping enough, eating right, exercising regularly — will do more to improve your productivity than any language or framework out there. Your ability to communicate and collaborate with others will always be a better predictor of success than your choice of technology stack. Your relationships with others are the most significant factors of success and happiness in life.

I stand by this advice, boring as it may be.

It’s been an exciting week, so take a moment to collect yourself. Go on a walk. Take a hike. (Be safe about it.) Do whatever you need to break free of the “reality distortion field”. Once you do, you’ll have the necessary distance to determine what new technologies you should pay attention to and what you can ignore for now.

We have a lot of work ahead of us.

As We May Code

$
0
0

Chris Lattner often describes LLVM as a process of lowering.

Swift Compiler Architecture Diagram

You start at the highest level of abstraction, source code written in a programming language like Swift or Objective-C. That code is parsed into an abstract syntax tree, (AST), which is progressively transformed into lower-level, intermediate representations until it finally becomes executable binary.

What if, instead of lowering source code down for the purpose of execution, we raised source code for the purpose of understanding?

You could say that we already do this to some degree withsyntax highlighting
(func f()funcf()),structured editing, anddocumentation generation. But how far could we take it?


In this article, I’d like to share an idea that I’ve been kicking around for a while. It’s something that’s come into greater focus with my recent work on swift-doc, but first started to form during tenure in Apple Developer Publications, back in 2015.

The idea is this:
What if we took the lessons of the semantic web and applied them to source code?

Specifically:

  • Representation: Software components should be represented by a common, language-agnostic data format.
  • Addressability: Packages, modules, and their constituent APIs should each have a unique URL identifier.
  • Decentralization: Information should be distributed across a federated network of data sources, which can cross-reference one another by URL.

I grew up with the Internet, and got to see it, first-hand, go from an obscure technology to the dominant cultural force. So much of what I see in software development today reminds me of what I remember about the web from 20 years ago. And if you’ll forgive the extended wind-up, I think there’s a lot we can learn by looking at that evolution.


Web 1.0 The Web of Documents

Tim Berners-Lee launched the World Wide Web from a NeXT workstation 27 years ago. His vision for a globally-distributed, decentralized network of inter-connected documents gave rise to the Internet as we know it today. But it was also part of an intellectual tradition dating back to the 1940s, which includes Vannevar Bush’s Memex, Ted Nelson’s Xanadu, and Doug Engelbart’s Mother of All Demos.

In those early days, the knowledge being shared was primarily academic. As the userbase grew over time, so too did the breadth and diversity of the information available. And, for a time, that’s what the Internet was: fan sites for Sunbeam toasters,recipes for Neapolitan-style pizza, andthe official website for the 1996 film Space Jam.

But the web of documents had limits.

If you wanted to shop for appliances, see the menu of a pizza shop, or get local showtimes for a movie, you might be able to do that on the early Internet. But you really had to work at it.

Back then, you’d start by going to a directory like Yahoo! or DMOZ, navigate to the relevant topic, and click around until you found a promising lead. Most of the time, you wouldn’t find what you were looking for; instead, you’d disconnect your modem to free up your landline and consult the yellow pages.

This started to change in the early ’00s.

Web 2.0 The Social Web

With Perl CGI and PHP, you could now easily generate web pages on-the-fly. This enabled eCommerce and the first commercial uses of the Internet.

After the ensuing dot-com bubble, you had technologies like Java applets and Flash bring a new level of interactivity to web sites. Eventually, folks figured out how to usean obscure API from Internet Explorer 5 to replicate this interactivity on normal webpages — a technique dubbed AJAX. Interacting with a page and seeing results live, without reloading a page? This was huge. Without that, social media might not have taken off as it did.

Anyway, the server-side APIs powering those AJAX interactions on the client, they were the secret sauce that let the Internet evolve into what it is today.

Remember mashups?

Thanks to all of these (often unsecured) AJAX endpoints, developers could synthesize information across multiple sources in ways that nobody had ever thought to do. You could get someone’s location from Fire Eagle, search for photos taken nearby on Flickr, and use MOO to print and deliver prints of them on-demand.

By the end of the decade, the rise of social networks and the early promise of mashups started to coalesce into the modern Internet.

Web 3.0 The Web of Data

The term “Web 3.0” didn’t catch on like its predecessor, but there’s a clear delineation between the technologies and culture of the web between the early and late ’00s.

It’s hard to overstate how much the iPhone’s launch in 2007 totally changed the trajectory of the Internet. But many other events played an important role in shaping the web as we know it today:

  • Google acquiring the company behind Freebase, giving it a knowledge graph to augment its website index.
  • Facebook launching Open Graph, which meant everything could now be “Liked” (and everyone could be targeted for advertisements).
  • Yahoo releasing SearchMonkey andBOSS, two ambitious (albeit flawed) attempts to carve out a niche from Google’s monopoly on search.
  • Wolfram launching Wolfram|Alpha, which far exceeded what many of us thought was possible for a question answering system.

The Internet always had a lot of information on it; the difference now is that the information is accessible to machines as well as humans.

Today, you can ask Google“Who was the first person to land on the moon?” and get an info box saying, “Commander Neil Armstrong”. You can post a link in Messages and see it represented bya rich visual summary instead of a plain text URL. You can ask Siri,“What is the airspeed velocity of an unladen swallow?” and hear back“I can’t get the answer to that on HomePod”About 25 miles per hour.

Think about what we take for granted about the Internet now, and try to imagine doing that on the web when it lookedlike this. It’s hard to think that any of this would be possible without the semantic web.


GitHub.com, Present Day The Spider and The Octocat

READMEs on GitHub.com today remind me of personal home pages on Geocities back in the Web 1.0 days.

Even with the standard coat of paint, you see an enormous degree of variance across projects and communities. Some are sparse; others are replete with adornment.

And yet, no matter what a project’s README looks like, onboarding onto a new tool or library entails, well reading.

GitHub offers some structured informational cues: language breakdown, license, some metadata about commit activity. You can search within the repo using text terms. And thanks to semantic / tree-sitter, you can even click through to find declarations in some languages.

But where’s a list of methods?Where are the platform requirements?
You have to read the README to find out! (Better hope it’s up-to-date 😭)

The modest capabilities of browsing and searching code today more closely resemble AltaVista circa 2000 than Google circa 2020. Theres so much more that we could be doing.


RDF Vocabularies The Owl and The Turtle

At the center of the semantic web is something calledRDF, the Resource Description Framework. It’s a collection of standards for representing and exchanging data. The atomic data entity in RDF is called a triple, which comprises:

  • a subject (“the sky”)
  • a predicate (“has the color”)
  • an object (“blue”)

You can organize triples according to avocabulary, or ontology, which defines rules about how things are described. RDF vocabularies are represented by the Web Ontology Language (OWL).

The ideas behind RDF are simple enough. Often, the hardest part is navigating its confusing, acronym-laden technology stack. The important thing to keep in mind is that information can be represented in several different ways without changing the meaning of that information.

Here’s a quick run-down:

RDF/XML
An XML representation format for RDF graphs.
JSON-LD
A JSON representation format for RDF graphs.
N-Triples
A plain text representation format for RDF graphs where each line encodes a subject–predicate–object triple.
Turtle
A human-friendly, plain text representation format for RDF graphs. A superset of N-Triples, and the syntax used in SPARQL queries.
SPARQL
A query language for RDF graphs.

Defining a Vocabulary

Let’s start to define a vocabulary for the Swift programming language. To start, we’ll define the concept of a Symbol along with two subclasses, Structure and Function. We’ll also define a name property that holds a token (a string) that applies to any Symbol. Finally, we’ll define a returns property that applies to a Function and holds a reference to another Symbol.

@prefix:<http://www.swift.org/#>.@prefixowl:<http://www.w3.org/2002/07/owl#>.@prefixrdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>.@prefixrdfs:<http://www.w3.org/2000/01/rdf-schema#>.@prefixxsd:<http://www.w3.org/2001/XMLSchema#>.:Symbolrdf:typeowl:Class.:namerdf:typeowl:FunctionalProperty;rdfs:domain:Symbol;rdfs:rangexsd:token.:Structurerdfs:subClassOf:Symbol.:Functionrdfs:subClassOf:Symbol.:returnsrdf:typeowl:FunctionalProperty;rdfs:domain:Function;rdfs:range:Symbol.

Parsing Code Declarations

Now consider the following Swift code:

structWidget{}funcfoo()->Widget{}funcbar()->Widget{}

We can use SwiftSyntax to parse the code into an AST and SwiftSemantics to convert those AST nodes into a more convenient representation.

importSwiftSyntaximportSwiftSemanticsvarcollector=DeclarationCollector()lettree=trySyntaxParser.parse(source:source)collector.walk(tree)collector.functions.first?.name// "foo()"collector.functions.first?.returns// "Widget"

Combining this syntactic reading with information from compiler, we can express facts about the code in the form of RDF triples.

{"@context":{"name":{"@id":"http://www.swift.org/#name","@type":"http://www.w3.org/2001/XMLSchema#token"},"returns":"http://www.swift.org/#returns"},"symbols":[{"@id":"E83C6A28-1E68-406E-8162-D389A04DFB27","@type":"http://www.swift.org/#Structure","name":"Widget"},{"@id":"4EAE3E8C-FD96-4664-B7F7-D64D8B75ECEB","@type":"http://www.swift.org/#Function","name":"foo()"},{"@id":"2D1F49FE-86DE-4715-BD59-FA70392E41BE","@type":"http://www.swift.org/#Function","name":"bar()"}]}

Encoding our knowledge into a standard format lets anyone access that information — however they like. And because these facts are encoded within an ontology, they can be validated for coherence and consistency. It’s totally language agnostic.

Querying the Results

With an RDF graph of facts, we can query it using SPARQL. Or, we could load the information into a graph database like Neo4j or a relational database like PostgreSQL and perform the query in Cypher or SQL, respectively.

PREFIXswift:<http://www.swift.org/#>SELECT?function?nameWHERE{?functionaswift:Function;swift:returns?type;swift:name?name.?typeswift:name"Widget".}ORDERBY?function

Whichever route we take, we get the same results:

idname
4EAE3E8C-FD96-4664-B7F7-D64D8B75ECEBfoo()
2D1F49FE-86DE-4715-BD59-FA70392E41BEbar()

Answering Questions About Your Code

“What can you do with a knowledge graph?” That’s kind of like asking, “What can you do with Swift?” The answer — “Pretty much anything”— is as true as it is unhelpful.

Perhaps a better framing would be to consider the kinds of questions that a knowledge graph of code symbols can help answer:

  • Which methods in Foundation produce a Date value?
  • Which public types in my project don’t conform to Codable?
  • Which methods does Array inherit default implementations from RandomAccessCollection?
  • Which APIs have documentation that includes example code?
  • What are the most important APIs in MapKit?
  • Are there any unused APIs in my project?
  • What’s the oldest version of iOS that my app could target based on my current API usage?
  • What APIs were added to Alamofire between versions 4.0 and 4.2?
  • What APIs in our app are affected by a CVE issued for a 3rd-party dependency?

The possibilities get even more interesting as you layer additional contexts by linking Swift APIs to different domains and other programming languages:

  • How is this Swift API exposed in Objective-C?
  • Who are the developers maintaining the packages that are pulled in as external dependencies for this project?
  • What’s the closest functional equivalent to this Swift package that’s written in Rust?

Future Applications The Promise of What Lies Ahead

Any fact becomes important when it’s connected to another.

Umberto Eco, Foucault’s Pendulum

Operating on code symbolically is more powerful than treating it as text. Once you’ve experienced proper refactoring tools, you’ll never want to go back to global find-and-replace.

The leap from symbolic to semantic understanding of code promises to be just as powerful. What follows are a few examples of potential applications of the knowledge graph we’ve described.

Flexible Search Queries

GitHub’s advanced search provides an interface to filter results on variousfacets, but they’re limited to metadata about the projects. You can search for Swift code written by @kateinoigakukun in 2020, but you can’t, for example, filter for code compatible with Swift 5.1. You can search code for the string “record”, but you can’t disambiguate between type and function definitions (class Record vs. func record()).

As we showed earlier, the kinds of queries we can perform across a knowledge graph are fundamentally different from what’s possible with a conventional faceted, full-text search index.

For example, here’s a SPARQL query to find the urls of repositories created by @kateinoigakukun and updated this year that contain Swift functions named record:

PREFIXswift:<http://www.swift.org/#>skos:<http://www.w3.org/2004/02/skos/core/#>sdo:<http://schema.org/#>SELECT?urlWHERE{?functionaswift:Function;swift:name"record";skos:member?repository.?repositoryasdo:SoftwareSourceCode;sdo:contributor?contributor;sdo:url?url;sdo:dateModified?date.?contributorasdo:Person;sdo:username"kateinoigakukun".FILTER(?date>="2020-01-01")}ORDERBY?url

Linked Documentation

When faced withmissing or incomplete documentation, developers are left to search Google for blog posts, tutorials, conference videos, and sample code to fill in the gaps. Often, this means sifting through pages of irrelevant results — to say nothing of outdated and incorrect information.

A knowledge graph can improve search for documentation much the same as it can for code, but we can go even further. Similar to how academic papers contain citations, example code can be annotated to include references to the canonical APIs it interacts with. Strong connections between references and its source material make for easy retrieval later on.

Imagine if, when you option-click on an API in Xcode to get its documentation, you also saw a list of sample code and WWDC session videos? Or what if we could generate sample code automatically from test cases? Wouldn’t that be nice?

All of that information is out there, just waiting for us to connect the dots.

Automatic µDependencies

John D. Cook onceobserved, code reuse is more like an organ transplant than snapping LEGO blocks together. Fred Brooks similarly analogized software developers to surgeons inThe Mythical Man-Month.

But that’s not to say that things can’t get better — it’d be hard to argue that they haven’t.

Web applications were once described in similar, organic terms, but that came to an end with the advent ofcontainerization. Now you can orchestrate entire multi-cloud deployments automatically via declarative configuration files.

Before CPAN, the state of the art for dependency management was copy-pasting chunks of codeyou found on a web page. But today, package managers are essential infrastructure for projects.


What if, instead of organizing code into self-contained, modular chunks ourselves, we let software do it for us? Call itFaaD (Functions as a Dependency).

Say you want an implementation ofk-means clustering. You might search around for “k-means” or “clustering” on GitHub and find a package named “SwiftyClusterAlgorithms” (😒), only to discover that it includes a bunch of functionality that you don’t need — and to add insult to injury, some of those extra bits happen to generate compiler warnings. Super annoying.

Today, there’s no automatic way to pick and choose what you need. (Swift import syntax (import func kMeans) is a lie) But there’s no inherent reason why the compiler couldn’t do this for you.

Or to go even further: If everything compiles down to web assembly, there’s no inherent requirement for that implementation of k-means — it could be written in Rust or JavaScript, and you’d be none the wiser.

At a certain point, you start to question the inherent necessity of software packaging as we know it today. Take it far enough, and you may wonder how much code we’ll write ourselves in the future.

Code Generation

A few months ago, Microsoft hosted its Build conference. And among the videos presented was an interview withSam Altman, CEO of OpenAI. A few minutes in, the interview cut to a video of Sam using a fine-tuned version ofGPT-2 towrite Python code from docstrings.

defis_palindrome(s):"""Check whether a string is a palindrome"""returns==s[::-1]# ← Generated by AI model from docstring!
        

And that’s using a model that treats code as text. Imagine how far you could go with a priori knowledge of programming languages! Unlike English, the rules of code are, well, codified. You can check to see if code compiles — and if it does compile, you can run it to see the results.

At this point, you should feel either very worried or very excited.
If you don’t, then you’re not paying attention.

Taking Ideas Seriously The Shoemaker’s Children

The use of FORTRAN, like the earlier symbolic programming, was very slow to be taken up by the professionals. And this is typical of almost all professional groups. Doctors clearly do not follow the advice they give to others, and they also have a high proportion of drug addicts. Lawyers often do not leave decent wills when they die. Almost all professionals are slow to use their own expertise for their own work. The situation is nicely summarized by the old saying,“The shoe maker’s children go without shoes”. Consider how in the future, when you are a great expert, you will avoid this typical error!

Richard W. Hamming, “The Art of Doing Science and Engineering”

Today, lawyers delegate many paralegal tasks like document discovery to computers and doctors routinely use machine learning models to help diagnose patients.

So why aren’t we — ostensibly the people writing software— doing more with AI in our day-to-day? Why are things like TabNine andKite so often seen as curiosities instead of game-changers?

If you take seriously the idea thatAI will fundamentally change the nature of many occupations in the coming decade, what reason do you have to believe that you’ll be immune from that because you work in software? Looking at the code you’ve been paid to write over the past few years, how much of that can you honestly say is truly novel?

We’re really not as clever as we think we are.


Postscript Reflection and Metaprogramming

Today marks 8 years since I started NSHipster.

You might’ve noticed that I don’t write here as much as I once did. And on the occasions that I do publish an article, it’s more likely to include obscure historical facts andcultural references than to the obscure APIs promised by this blog’s tagline.

A few weeks out now from WWDC, I should be writing aboutDCAppAttestService,SKTestSession, SwiftUI Namespace andUTType. But here we are, at the end of an article about the semantic web, of all things…


The truth is, I’ve come around to thinking that programming isn’t the most important thing for programmers to pay attention to right now.


Anyway, I’d like to take this opportunity to extend my sincere gratitude to everyone who reads the words I write. Thank you. It may be a while before I get back into a regular cadence, so apologies in advance.

Until next time,May your code continue to compile and inspire.


op run

$
0
0

.env files. If you’ve worked on a web application, you’ve probably seen one.

While they certainly get the job done, .env files have shortcomings that can create friction in development workflows.

We’ve touched on .env files in past articles about xcconfig files and secret management on iOS. But this week on NSHipster we’re taking a deeper look, exploring how the lesser-known 1Password CLI (op) can solve some problems many of us face managing secrets day-to-day.


The Problem of Configuration

Around 2011, Adam Wiggins published “The Twelve-Factor App”, a methodology for building modern web applications that has since become canon in our industry.

The third of those twelve factors, “Config”, prescribes storing configuration in environment variables:

“Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.”

This core insight — that configuration should be separate from code — led to the widespread adoption of .env files.

A typical .env file looks something like this:

DATABASE_URL=postgres://localhost:5432/myapp_development
        REDIS_URL=redis://localhost:6379/0
        AWS_ACCESS_KEY_ID=AKIA...
        AWS_SECRET_ACCESS_KEY=wJa...
        STRIPE_SECRET_KEY=sk_test_...
        

You add this file to .gitignore to keep it out of version control, and load these variables into your environment at runtime with a tool or library.

Simple enough. So what’s the problem?

.env Files in Practice

Despite their apparent simplicity, .env files introduce several points of friction in development workflows:

First, there’s the perennial issue of onboarding: How does a new team member get what they need to run the app locally? The common solution is to have a .env.sample / .env.example file in version control, but this creates a maintenance burden to keep it in sync with the actual requirements. And in any case, developers still need to go on a scavenger hunt to fill it out before they can be productive.

Then there’s the multi-environment problem: As soon as you need different configurations for development, staging, and production, you end up with a proliferation of files: .env.development, .env.test, .env.staging… Each requiring its own .sample / .example counterpart.

But perhaps most pernicious is the challenge of managing changes to configuration over time. Because .env files aren’t in version control, changes aren’t, you know… tracked anywhere🥲

Enter the 1Password CLI (op)

You may already use 1Password to manage your passwords and other secrets. But what you might not know is that 1Password also has a CLI that can integrate directly with your development workflow.

op lets you manage 1Password from the command-line. You can do all the CRUD operations you’d expect for items in your vault. But its killer features is the op run subcommand, which can dynamically inject secrets from your 1Password vault into your application’s environment.

Instead of storing sensitive values directly in your .env file, you reference them using special op:// URLs:

# .envIRC_USERNAME=op://development/chatroom/username
        IRC_PASSWORD=op://development/chatroom/password
        

importFoundationguardletusername=ProcessInfo.processInfo.environment["IRC_USERNAME"],letpassword=ProcessInfo.processInfo.environment["IRC_PASSWORD"]else{fatalError("Missing required environment variables")}// For testing only - never print credentials in production codeprint(password)

Run this on its own, and you’ll fail in proper 12 Factor fashion:

$swift run
        ❗️ "Missing required environment variables"

But by prepending op run we read in that .env file, resolve each vault item reference, and injects those values into the evironment:

$op run -- swift run
        hunter2
        

You’re even prompted to authorize with Touch ID the first time you invoke op run.

1Password Create Vault dialog

Ready to give this a test drive? Here’s how to get started:

A Step-By-Step Guide to Using the 1Password CLI in .env Files

Step 1: Install and Configure the 1Password CLI

On macOS, you can install the CLI with homebrew:

$brew install 1password-cli
        

Then, in the 1Password app, open Settings (,), go to the Developer section, and check the box labeled “Integrate with 1Password CLI”.

1Password Settings

Running any op subcommand should prompt you to connect to the app.

If you get off the happy path, consult the official docs to get back on track.

Step 2: Create a Shared Vault

Create a new vault in 1Password specifically for development secrets. Give it a clear name like “Development” and a useful description.

1Password Create Vault dialog

Step 3: Migrate Existing Secrets

For each entry in your .env file, create a corresponding item in 1Password. Choose the appropriate item type:

API Credential
For third-party service API keys
Fields: username, credential
Password
For first-party secrets, like encryption keys
Fields: username, password
Database
For hosted PostgreSQL databases and the like
Fields: type, server, port, database, username, password

Step 4: Update Your .env File

Replace raw values in your .env file with op:// references using the following format:

op://vault/item/field

Each reference consists of three components:

  • The vault name (e.g., “development”)
  • The item name or UUID
  • The field name from the item

For example, here’s how you might reference credentials for various services:

# Reference by item name (case-insensitive)AWS_ACCESS_KEY_ID=op://development/AWS/username
        AWS_SECRET_ACCESS_KEY=op://development/WorkOS/credential
        # Reference by item UUIDSTRIPE_SECRET_KEY=op://development/abc123xyz789defghijklmnop/password
        # Using different field names based on item typeDATABASE_HOST=op://development/db/server
        DATABASE_USER=op://development/db/username
        DATABASE_PASSWORD=op://development/db/password
        DATABASE_NAME=op://development/db/database
        

You can locate the UUID for any item in 1Password by clicking the "More actions" button (, whatever you want to call that) and selecting "Copy item UUID".

Both item name and UUID references work, but using UUIDs can be more reliable in automation contexts since they're guaranteed to be unique and won't change if you rename the item.

1Password Copy Item UUID

Once you’ve replaced all sensitive values with op:// references, you can safely commit your .env file to version control. The references themselves don’t contain any sensitive information – they’re just pointers to your 1Password vault.

Step 5. Update Your Development Script

Whatever command you normally run to kick off your development server, you’ll need to prepend op run -- to that.

For example, if you follow the “Scripts to Rule Them All” pattern, you’d update script/start like so:

#!/bin/sh- swift run
        + op run -- swift run
        

Advantages Over Traditional .env Files

op run solves many of the problems inherent to .env files:

  • No More Cold Start Woes: New team members get access to all required configuration simply by joining the appropriate 1Password vault.

  • Automatic Updates: When credentials change, they’re automatically updated for everyone on the team. No more out-of-sync configuration.

  • Proper Secret Management: 1Password provides features like access controls, versioning, and integration with Have I Been Pwned.

Potential Gotchas

Like any technical solution, there are some trade-offs to consider:

  • Performance: op run adds a small overhead to command startup time (typically less than a second). 1

  • stdout/stderr Handling: As mentioned above, op run modifies stdout/stderr to implement secret masking, which can interfere with some terminal applications. 2

  • Dev Container Support: If you use VSCode Dev Containers, you may encounter some friction with the 1Password CLI. 3

Driving Technical Change

The implementation is often the easy part. The real challenge can be getting your team on board with the change.

First, state the problem you’re trying to solve. Change for change’s sake is rarely helpful.

Next, figure out who you need to get buy-in from. Talk to them. Articulate specific pain point that everyone recognizes, like the frustration of onboarding new team members or the time wasted debugging configuration-related issues.

Once you’ve gotten the green light, move slowly but deliberately. Start small by migrating a single credential, or maybe all of the credentials in a smaller project. Build up confidence that this approach is a good fit — both technically and socially.


Managing development secrets is one of those problems that seems trivial at first but can become a significant source of friction as your team and application grow.

The 1Password CLI offers a more sophisticated approach that integrates with tools developers already use and trust.

While it may not be the right solution for every team, it’s worth considering if you’re feeling the pain of traditional .env files.

Ollama

$
0
0

“Only Apple can do this” Variously attributed to Tim Cook

Apple introduced Apple Intelligence at WWDC 2024. After waiting almost a year for Apple to, in Craig Federighi’s words, “get it right”, its promise of “AI for the rest of us” feels just as distant as ever.

While we wait for Apple Intelligence to arrive on our devices, something remarkable is already running on our Macs. Think of it as a locavore approach to artificial intelligence: homegrown, sustainable, and available year-round.

This week on NSHipster, we’ll look at how you can use Ollama to run LLMs locally on your Mac — both as an end-user and as a developer.


What is Ollama?

Ollama is the easiest way to run large language models on your Mac. You can think of it as “Docker for LLMs” - a way to pull, run, and manage AI models as easily as containers.

Download Ollama with Homebrew or directly from their website. Then pull and run llama3.2 (2GB).

$brew install--cask ollama
        $ollama run llama3.2
        >>> Tell me a joke about Swift programming.
        What's a Apple developer's favorite drink? 
        The Kool-Aid.
        

Under the hood, Ollama is powered by llama.cpp. But where llama.cpp provides the engine, Ollama gives you a vehicle you’d actually want to drive — handling all the complexity of model management, optimization, and inference.

Similar to how Dockerfiles define container images, Ollama uses Modelfiles to configure model behavior:

FROM mistral:latest
        PARAMETER temperature 0.7
        TEMPLATE """
        You are a helpful assistant.
        User: 
        Assistant: """

Ollama uses the Open Container Initiative (OCI) standard to distribute models. Each model is split into layers and described by a manifest, the same approach used by Docker containers:

{"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.ollama.image.config.v1+json","digest":"sha256:..."},"layers":[{"mediaType":"application/vnd.ollama.image.layer.v1+json","digest":"sha256:...","size":4019248935}]}

Overall, Ollama’s approach is thoughtful and well-engineered. And best of all, it just works.

What’s the big deal about running models locally?

Jevons paradox states that, as something becomes more efficient, we tend to use more of it, not less.

Having AI on your own device changes everything. When computation becomes essentially free, you start to see intelligence differently.

While frontier models like GPT-4 and Claude are undeniably miraculous, there’s something to be said for the small miracle of running open models locally.

  • Privacy: Your data never leaves your device. Essential for working with sensitive information.
  • Cost: Run 24/7 without usage meters ticking. No more rationing prompts like ’90s cell phone minutes. Just a fixed, up-front cost for unlimited inference.
  • Latency: No network round-trips means faster responses. Your /M\d Mac((Book( Pro| Air)?)|Mini|Studio)/ can easily generate dozens of tokens per second. (Try to keep up!)
  • Control: No black-box RLHF or censorship. The AI works for you, not the other way around.
  • Reliability: No outages or API quota limits. 100% uptime for your exocortex. Like having Wikipedia on a thumb drive.

Building macOS Apps with Ollama

Ollama also exposes an HTTP API on port 11431 (leetspeak for llama 🦙). This makes it easy to integrate with any programming language or tool.

To that end, we’ve created the Ollama Swift package to help developers integrate Ollama into their apps.

Text Completions

The simplest way to use a language model is to generate text from a prompt:

importOllamaletclient=Client.defaultletresponse=tryawaitclient.generate(model:"llama3.2",prompt:"Tell me a joke about Swift programming.",options:["temperature":0.7])print(response.response)// How many Apple engineers does it take to document an API? // None - that's what WWDC videos are for.

Chat Completions

For more structured interactions, you can use the chat API to maintain a conversation with multiple messages and different roles:

letinitialResponse=tryawaitclient.chat(model:"llama3.2",messages:[.system("You are a helpful assistant."),.user("What city is Apple located in?")])print(initialResponse.message.content)// Apple's headquarters, known as the Apple Park campus, is located in Cupertino, California.// The company was originally founded in Los Altos, California, and later moved to Cupertino in 1997.letfollowUp=tryawaitclient.chat(model:"llama3.2",messages:[.system("You are a helpful assistant."),.user("What city is Apple located in?"),.assistant(initialResponse.message.content),.user("Please summarize in a single word")])print(followUp.message.content)// Cupertino

Generating text embeddings

Embeddings convert text into high-dimensional vectors that capture semantic meaning. These vectors can be used to find similar content or perform semantic search.

For example, if you wanted to find documents similar to a user’s query:

letdocuments:[String]=// Convert text into vectors we can compare for similarityletembeddings=tryawaitclient.embeddings(model:"nomic-embed-text",texts:documents)/// Finds relevant documentsfuncfindRelevantDocuments(forquery:String,threshold:Float=0.7,// cutoff for matching, tunablelimit:Int=5)asyncthrows->[String]{// Get embedding for the querylet[queryEmbedding]=tryawaitclient.embeddings(model:"llama3.2",texts:[query])// See: https://en.wikipedia.org/wiki/Cosine_similarityfunccosineSimilarity(_a:[Float],_b:[Float])->Float{letdotProduct=zip(a,b).map(*).reduce(0,+)letmagnitude={sqrt($0.map{$0*$0}.reduce(0,+))}returndotProduct/(magnitude(a)*magnitude(b))}// Find documents above similarity thresholdletrankedDocuments=zip(embeddings,documents).map{embedding,documentin(similarity:cosineSimilarity(embedding,queryEmbedding),document:document)}.filter{$0.similarity>=threshold}.sorted{$0.similarity>$1.similarity}.prefix(limit)returnrankedDocuments.map(\.document)}

Building a RAG System

Embeddings really shine when combined with text generation in a RAG (Retrieval Augmented Generation) workflow. Instead of asking the model to generate information from its training data, we can ground its responses in our own documents by:

  1. Converting documents into embeddings
  2. Finding relevant documents based on the query
  3. Using those documents as context for generation

Here’s a simple example:

letquery="What were AAPL's earnings in Q3 2024?"letrelevantDocs=tryawaitfindRelevantDocuments(query:query)letcontext="""
        Use the following documents to answer the question. 
        If the answer isn't contained in the documents, say so.
        Documents:
        \(relevantDocs.joined(separator:"\n---\n"))
        Question: \(query)"""letresponse=tryawaitclient.generate(model:"llama3.2",prompt:context)

To summarize: Different models have different capabilities.

  • Models like llama3.2 and deepseek-r1 generate text.
    • Some text models have “base” or “instruct” variants, suitable for fine-tuning or chat completion, respectively.
    • Some text models are tuned to support tool use, which let them perform more complex tasks and interact with the outside world.
  • Models like llama3.2-vision can take images along with text as inputs.

  • Models like nomic-embed-text create numerical vectors that capture semantic meaning.

With Ollama, you get unlimited access to a wealth of these and many more open-source language models.


So, what can you build with all of this?
Here’s just one example:

Nominate.app

Nominate is a macOS app that uses Ollama to intelligently rename PDF files based on their contents.

Like many of us striving for a paperless lifestyle, you might find yourself scanning documents only to end up with cryptically-named PDFs like Scan2025-02-03_123456.pdf. Nominate solves this by combining AI with traditional NLP techniques to automatically generate descriptive filenames based on document contents.


The app leverages several technologies we’ve discussed:

  • Ollama’s API for content analysis via the ollama-swift package
  • Apple’s PDFKit for OCR
  • The Natural Language framework for text processing
  • Foundation’s DateFormatter for parsing dates

Looking Ahead

“The future is already here – it’s just not evenly distributed yet.” William Gibson

Think about the timelines:

  • Apple Intelligence was announced last year.
  • Swift came out 10 years ago.
  • SwiftUI 6 years ago.

If you wait for Apple to deliver on its promises, you’re going to miss out on the most important technological shift in a generation.

The future is here today. You don’t have to wait. With Ollama, you can start building the next generation of AI-powered apps right now.

Model Context Protocol (MCP)

$
0
0

Language Server Protocol (LSP) revolutionized how programming languages integrate with developer tools. Model Context Protocol (MCP) aims to do the same for a new generation of AI tools.

But before we lay our scene of two protocols (both alike in dignity), let’s take a moment to motivate the problem a bit more.

Tool Use & Agents

Today’s frontier models are extraordinary. But they’re limited in two key ways:

  1. They don’t know anything outside their training data
  2. They can’t do anything outside of predicting the next token

To their credit, LLMs (kinda, sorta) understand these limitations, and have been trained to ask for what they need. We (humans) have settled on a practice called tool use that lets AI do just that.

Here’s an example:

User
Write a limerick about today's weather in Portland, Oregon.
Client, to Assistant →
Also, there's this "Get Weather" tool available if you need it. It takes a set of coordinates and returns the current temperature and conditions.
Assistant
<thinking>To write an accurate poem, I should get the weather. From my training, I know Portland's coordinates to be 45.5155° N, 122.6789° W</thinking>
Tool call: {tool: "Get Weather", latitude: 45.5155, longitude: -122.6789}
← Client, to User
Is it alright to make that tool call?
User
👍
Client, to Assistant →
Tool result: {temperature: 12, conditions: 'cloudy', humidity: 85%}
Assistant
Here's your limerick about Portland's weather:
There once was a city called Portland
Where clouds made the skies quite importland
At twelve degrees C
It's as gray as can be
With a dampness that's truly absorbland!

Pretty clever, right?
But then again, so what?

On its face, this is a very slow, expensive way to do something better-served by a quick Google search — or sticking your head out a window, for that matter.

But here’s the thing:
Tool use gives rise to agency (✨𝓮𝓶𝓮𝓻𝓰𝓮𝓷𝓬𝓮✨)

As the saying goes, Quantity has a quality all its own. Give a language model a dozen… a hundred… a thousand tools — hell, give it a tool for making more tools. How confident are you that you could find a problem that couldn’t be solved by such a system?

We’re only just starting to see what’s possible.


But back to more practical matters:
Let’s talk about where we are today with Model Context Protocol.


The New M × N Problem

We’ve written previously about Language Server Protocol, and the M × N problem. LSP’s challenge was connecting M editors with N programming languages MCP faces a similar challenge, of connecting M clients with N resources. Without MCP, each AI application must implement custom integrations for every data source it wants to access.

This creates the same kind of fragmented ecosystem that plagued development tools before LSP:

  • Some AI applications offer deep integration with specific data sources but limited support for others
  • Different applications implement similar integrations in incompatible ways
  • Improvements to one integration rarely benefit the broader ecosystem

Like LSP, MCP transforms this M × N problem into an M + N problem through standardization. Rather than each AI application implementing custom integrations, it only needs to support the MCP standard. In doing so, it gains access to all MCP-compatible data sources and tools 🌈

How Model Context Protocol Works

MCP follows a client-server architecture similar to LSP:

  • The client is typically an AI application or development environment
    For example, Claude Desktop, Zed, and Cursor.
  • The server is a program that provides access to data and/or tools

Requests and responses are encoded according to the JSON-RPC 2.0 specification. Communication between client and server happens over Stdio (stdin/stdout) or HTTP with Server-Sent Events transports.

Like LSP, MCP has clients and servers negotiate a set of capabilities. When a client connects to a server, it sends an initialize message, with information about what protocol version it supports. The server responds in kind.

From there, the client can ask the server about what features it has. MCP describes three different kinds of features that a server can provide:

  • Prompts: Templates that shape how language models respond. They’re the difference between getting generic text and precise, useful results. A good prompt is like a well-designed API - it defines the contract between you and the model.
  • Resources: Reference materials that ground models in reality. By providing structured data alongside your query, you transform a model from a creative writer into an informed assistant with domain-specific knowledge. (Think: databases, file systems, documents)
  • Tools: Functions that extend what models can do. They allow AI to calculate, retrieve information, or interact with external systems when simple text generation isn’t enough. Tools bridge the gap between language understanding and practical capability.

Our previous example handwaved the existence of a “Get Weather” tool. MCP gives our client a standard way to consult various connected services.

To get a list of available tools on an MCP, the client would send a tools/list request to the server:

{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}

In our example, the server would respond:

{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_weather","description":"Returns current weather conditions for the specified coordinates.","inputSchema":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"}},"required":["latitude","longitude"]}}]}}

The client can share this list of tools with the language model in a system prompt or a user message. When the model responds wanting to invoke the get_weather tool, the client asks the user to confirm tool use. If the human-in-the-loop says 🆗, the client sends a tools/call request:

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"latitude":45.5155,"longitude":-122.6789}}}

In response, the server sends:

{"jsonrpc":"2.0","id":2,"content":[{"type":"text","text":"{\"temperature\": 12, \"conditions\": \"cloudy\", \"humidity\": 85}""annotations":{"audience":["assistant"]}}]}

The client then passes that result to the AI assistant, the assistant generates a response with this information, and the client passes that along to the user.


That’s pretty much all there is to it. There are plenty of details to get bogged down with. But that’s what LLMs are for. Now is the time for vibes coding.
MCP is punk rock.

How do I start?

MCP is an emerging standard from Anthropic. So it’s no surprise that Claude Desktop is most capable of showing off what it can do.

Once you have Claude Desktop installed, you can peruse the myriad example servers available.

Or, if you want to skip straight to la crème de la crème, then have a taste of what we’ve been cooking up with MCP lately:

iMCP

Fun fact! The word “paradise” derives from an old word for “walled garden”.

Ironic how Apple has a way of making your digital life a living hell sometimes.

For many of us who exist in Apple’s walled garden, we’re often frustrated by the product design and software quality that gets between us and our data. Spotlight search is stuck in the ‘00s. Apple Intelligence didn’t live up to the hype. Siri seems doomed to suck forever.

That was our motivation for building iMCP.

iMCP is a macOS app for connecting your digital life with AI. It works with Claude Desktop and a growing list of clients that support MCP. It gives MCP access to your calendars, contacts, even messages — no small feat!

Download it today and get a taste of some real Apple intelligence.

mcp-swift-sdk

In the process of building iMCP, we built a Swift SDK for Model Context Protocol servers and clients.

If you’re inspired to build your own MCP app and like working in Swift more than Python or TypeScript, definitely give this a try!

hype

If, however, you have accepted Python into your heart as I have, then I’d recommend checking out another project I’ve been working on: hype.

My goal with hype is to eliminate every barrier between writing Python code and calling it in a way that’s useful. Add the @hype.up decorator to a function to instantly generate an HTTP API, a CLI, a GUI, or an MCP.

# example.py
        importhypefrompydanticimportField@hype.updefdivide(x:int,y:int=Field(gt=0),)->int:"""
        Divides one number by another.
        :param x: The numerator
        :param y: The denominator
        :return: The quotient
        """returnx//y

Start up an MCP server with the hype command:

$hype mcp example.py
        

emcee

But really, the best code is code you don’t have to write. If you already have a web application with an OpenAPI specification, you can use another tool we built — emcee— to instantly spin up an MCP server to it.

emcee

We think emcee is a convenient way to connect to services that don’t have an existing MCP server implementation — especially for services you’re building yourself. Got a web app with an OpenAPI spec? You might be surprised how far you can get without a dashboard or client library.


In case it’s not abundantly clear, we here at NSHipster dot com are pretty bought into the promise of Model Context Protocol. And we’re excited to see where everything goes in the coming months.

If you’re building in this space, I’d love to hear from you✌️

Viewing all 71 articles
Browse latest View live