Software development best practicesprescribe
strict separation of configuration from code.
Yet developers on Apple platforms
often struggle to square these guidelines with Xcode’s project-heavy workflow.
Understanding what each project setting does
and how they all interact with one another
is a skill that can take years to hone.
And the fact that much of this information
is buried deep within the GUIs of Xcode does us no favors.
Navigate to the “Build Settings” tab of the project editor,
and you’ll be greeted by hundreds of build settings
spread across layers of projects, targets, and configurations —
and that’s to say nothing of the other six tabs!
Fortunately,
there’s a better way to manage all of this configuration
that doesn’t involve clicking through a maze of tabs and disclosure arrows.
This week,
we’ll show you how you can use text-based xcconfig files
to externalize build settings from Xcode
to make your projects more compact, comprehensible, and powerful.
Xcode build configuration files,
more commonly known by their xcconfig file extension,
allow build settings for your app to be declared and managed without Xcode.
They’re plain text,
which means they’re much friendlier to source control systems
and can be modified with any editor.
Fundamentally,
each configuration file consists of a sequence of key-value assignments
with the following syntax:
BUILD_SETTING_NAME=value
For example,
to specify the Swift language version for a project,
you’d specify the SWIFT_VERSION build setting like so:
SWIFT_VERSION=5.0
At first glance,xcconfig files bear a striking resemblance to .env files,
with their simple, newline-delimited syntax.
But there’s more to Xcode build configuration files than meets the eye.
Behold!
Retaining Existing Values
To append rather than replace existing definitions,
use the $(inherited) variable like so:
BUILD_SETTING_NAME=$(inherited)additional value
You typically do this to build up lists of values,
such as the paths in which
the compiler searches for frameworks
to find included header files
(FRAMEWORK_SEARCH_PATHS):
FRAMEWORK_SEARCH_PATHS=$(inherited)$(PROJECT_DIR)
Xcode assigns inherited values in the following order
(from lowest to highest precedence):
Platform Defaults
Xcode Project xcconfig File
Xcode Project File Build Settings
Target xcconfig File
Target Build Settings
Referencing Values
You can substitute values from other settings
by their declaration name
with the following syntax:
BUILD_SETTING_NAME=$(ANOTHER_BUILD_SETTING_NAME)
Substitutions can be used to
define new variables according to existing values,
or inline to build up new values dynamically.
Setting Fallback Values for Referenced Build Settings
In Xcode 11.4 and later,
you can use the default evaluation operator
to specify a fallback value to use
if the referenced build setting evaluates as empty.
$(BUILD_SETTING_NAME:default=value)
Conditionalizing Build Settings
You can conditionalize build settings according to their
SDK (sdk), architecture (arch), and / or configuration (config)
according to the following syntax:
BUILD_SETTING_NAME[sdk=sdk] =value for specified sdkBUILD_SETTING_NAME[arch=architecture] =value for specified architectureBUILD_SETTING_NAME[config=configuration] =value for specified configuration
Given a choice between multiple definitions of the same build setting,
the compiler resolves according to specificity.
BUILD_SETTING_NAME[sdk=sdk][arch=architecture] =value for specified sdk and architecturesBUILD_SETTING_NAME[sdk=*][arch=architecture] =value for all other sdks with specified architecture
For example,
you might specify the following build setting
to speed up local builds by only compiling for the active architecture:
Including Build Settings from Other Configuration Files
A build configuration file can include settings from other configuration files
using the same #include syntax
as the equivalent C directive
on which this functionality is based:
#include "path/to/File.xcconfig"
As we’ll see later on in the article,
you can take advantage of this to build up cascading lists of build settings
in really powerful ways.
Creating Build Configuration Files
To create a build configuration file,
select the “File > New File…” menu item (⌘N),
scroll down to the section labeled “Other”,
and select the Configuration Settings File template.
Next, save it somewhere in your project directory,
making sure to add it to your desired targets
Once you’ve created an xcconfig file,
you can assign it to one or more build configurations
for its associated targets.
Now that we’ve covered the basics of using Xcode build configuration files
let’s look at a couple of examples of how you can use them
to manage development, stage, and production environments.
Customizing App Name and Icon for Internal Builds
Developing an iOS app usually involves
juggling various internal builds
on your simulators and test devices
(as well as the latest version from the App Store,
to use as a reference).
You can make things easier on yourself
with xcconfig files that assign each configuration
a distinct name and app icon.
If your backend developers comport themselves according to the aforementioned12 Factor App philosophy,
then they’ll have separate endpoints for
development, stage, and production environments.
On iOS,
perhaps the most common approach to managing these environments
is to use conditional compilation statements
with build settings like DEBUG.
However,
to pull these values programmatically,
we’ll need to take one additional step:
Accessing Build Settings from Swift
Build settings defined by
the Xcode project file, xcconfig files, and environment variables,
are only available at build time.
When you run the compiled app,
none of that surrounding context is available.(And thank goodness for that!)
But wait a sec —
don’t you remember seeing some of those build settings before
in one of those other tabs?
Info, was it?
As it so happens,
that info tab is actually just a fancy presentation of
the target’s Info.plist file.
At build time,
that Info.plist file is compiled
according to the build settings provided
and copied into the resulting app bundle.
Therefore,
by adding references to $(API_BASE_URL),
you can access the values for those settings
through the infoDictionary property of Foundation’s Bundle API.Neat!
Following this approach,
we might do something like the following:
Xcode projects are monolithic, fragile, and opaque.
They’re a source of friction for collaboration among team members
and generally a drag to work with.
Fortunately,xcconfig files go a long way to address these pain points.
Moving configuration out of Xcode and into xcconfig files
confers a multitude of benefits
and offers a way to distance your project from the particulars of Xcode
without leaving the Cupertino-approved “happy path”.
In 2002,
the United States Congress enacted
the Sarbanes–Oxley Act,
which introduced broad oversight to corporations
in response to accounting scandals at companies likeEnron and MCI WorldCom
around that time.
This act,PCI
and
HIPAA,
formed the regulatory backdrop
for a new generation ofIT companies
emerging from the dot-com bubble.
Around the same time,
we saw the emergence of ephemeral, distributed infrastructure —
what we now call “Cloud computing”—
a paradigm that made systems more capable but also more complex.
To solve both the regulatory and logistical challenges of the 21st century,
our field established best practices around application logging.
And many of the same tools and standards are still in use today.
This week on NSHipster,
we’re taking a look at
SwiftLog:
a community-driven, open-source standard for logging in Swift.
Developed by the Swift on Server community
and endorsed by theSSWG (Swift Server Work Group),
its benefit isn’t limited to use on the server.
Indeed,
any Swift code intended to be run from the command line
would benefit from adopting SwiftLog.
Read on to learn how.
As always,
an example would be helpful in guiding our discussion.
In the spirit of transparency and nostalgia,
let’s imagine writing a Swift program
that audits the finances of a ’00s Fortune 500 company.
importFoundationstructAuditor{funcwatch(_directory:URL)throws{…}funccleanup(){…}}do{letauditor=Auditor()defer{auditor.cleanup()}tryauditor.watch(directory:URL(string:"ftp://…/reports")!,extensions:["xls","ods","qdf"])// poll for changes}catch{print("error: \(error)")}
An Auditor type polls for changes to a directory(an FTP server, because remember: it’s 2003).
Each time a file is added, removed, or changed,
its contents are audited for discrepancies.
If any financial oddities are encountered,
they’re logged using the print function.
The same goes for issues connecting to the FTP,
or any other problems the program might encounter —
everything’s logged using print.
Simple enough.
We can run it from the command line like so:
$swift run auditstarting up...
ERROR: unable to reconnect to FTP#(try again after restarting PC under our desk)$swift run audit+ connected to FTP server
! accounting discrepancy in balance sheet
** Quicken database corruption! **
^C
shutting down...
Such a program might be technically compliant,
but it leaves a lot of room for improvement:
For one,
our output doesn’t have any timestamps associated with it.
There’s no way to know whether a problem was detected an hour ago or last week.
Another problem is that our output lacks any coherent structure.
At a glance,
there’s no straightforward way to isolate program noise from real issues.
Finally, —
and this is mostly due to an under-specified example—
it’s unclear how this output is handled.
Where is this output going?
How is it collected, aggregated, and analyzed?
The good news is that
all of these problems (and many others) can be solved
by adopting a formal logging infrastructure in your project.
Adopting SwiftLog in Your Swift Program
Adding SwiftLog to an existing Swift package is 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.
In POSIX systems,
programs operate on three, predefinedstreams:
File Handle
Description
Name
0
stdin
Standard Input
1
stdout
Standard Output
2
stderr
Standard Error
By default,Logger uses the built-in StreamLogHandler type
to write logged messages to standard output (stdout).
We can override this behavior to instead write to standard error (stderr)
by using the more complex initializer,
which takes a factory parameter:
a closure that takes a single String parameter (the label)
and returns an object conforming to LogHandler.
Replacing Print Statements with Logging Statements
Declaring our logger as a top-level constant
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:
Level
Description
.trace
Appropriate for messages that contain information only when debugging a program.
.debug
Appropriate for messages that contain information normally of use only when debugging a program.
.info
Appropriate for informational messages.
.notice
Appropriate for conditions that are not error conditions, but that may require special handling.
.warning
Appropriate for messages that are not error conditions, but more severe than .notice
.error
Appropriate for error conditions.
.critical
Appropriate for critical error conditions that usually require immediate attention.
If we re-run our audit example with our new logging framework in place,
we can see the immediate benefit of clearly-labeled, distinct severity levels
in log lines:
$swift run audit2020-03-26T09:40:10-0700 critical: Couldn't connect to ftp://…#(try again after plugging in loose ethernet cord)$swift run audit2020-03-26T10:21:22-0700 warning: Discrepancy in balance sheet
2020-03-26T10:21:22-0700 error: Quicken database corruption
^C
Beyond merely labeling messages,which — don’t get us wrong — is sufficient benefit on its own,
log levels provide a configurable level of disclosure.
Notice that the messages logged with the trace method
don’t appear in the example output.
That’s because Logger defaults to showing only messages
logged as info level or higher.
You can configure that by setting the Logger’s logLevel property.
A twelve-factor app never concerns itself with
routing or storage of its output stream.
It should not attempt to write to or manage logfiles.
Instead, each running process writes its event stream, unbuffered, to stdout.
Collecting, routing, indexing, and analyzing logs across a distributed system
often requires a constellation of open-source libraries and commercial products.
Fortunately,
most of these components traffic in a shared currency ofsyslog messages —
and thanks to
this package by Ian Partridge,
Swift can, as well.
That said,
few engineers have managed to retrieve this information
from the likes of Splunk
and lived to tell the tale.
For us mere mortals,
we might preferthis package by Will Lisac,
which sends log messages toSlack.
The good news is that we can use both at once,
without changing how messages are logged at the call site
by using another piece of the Logging module:MultiplexLogHandler.
With all of this in place,
our system will log everything in syslog format to standard out (stdout),
where it can be collected and analyzed by some other system.
But the real strength of this approach to logging
is that it can be extended to meet the specific needs of any environment.
Instead of writing syslog to stdout or Slack messages,
your system could send emails,
open SalesForce tickets,
or trigger a webhook to activate some IoT device.
Here’s how you can extend SwiftLog to fit your needs
by writing a custom log handler:
Creating a Custom Log Handler
The LogHandler protocol specifies the requirements for types
that can be registered as message handlers by Logger:
In the process of writing this article,
I created custom handler
that formats log messages for GitHub Actions
so that they’re surfaced on GitHub’s UI like so:
If you’re interested in making your own logging handler,
you can learn a lot by just browsing
the code for this project.
But I did want to call out a few points of interest here:
Conditional Boostrapping
When bootstrapping your logging system,
you can define some logic for how things are configured.
For logging formatters specific to a particular CI vendor,
for example,
you might check the environment to see if you’re running locally or on CI
and adjust accordingly.
importLoggingimportLoggingGitHubActionsimportstructFoundation.ProcessInfoLoggingSystem.bootstrap{labelin// Are we running in a GitHub Actions workflow?ifProcessInfo.processInfo.environment["GITHUB_ACTIONS"]=="true"{returnGitHubActionsLogHandler.standardOutput(label:label)}else{returnStreamLogHandler.standardOutput(label:label)}}
Testing Custom Log Handlers
Testing turned out to be more of a challenge than originally anticipated.
I could be missing something obvious,
but there doesn’t seem to be a way to create assertions about
text written to standard output.
So here’s what I did instead:
First,
create an internal initializer that takes a TextOutputStream parameter,
and store it in a private property.
Then,
in the test target,
create a type that adopts TextOutputStream
and collects logged messages to a stored property
for later inspection.
By using a @testable import
of the module declaring GitHubActionsLogHandler,
we can access that internal initializer from before,
and pass an instance of MockTextOutputStream to intercept logged messages.
April is the month when apple trees start to bloom
up here in the Pacific Northwest.
All across Oregon’s Willamette Valley,
from Portland stretching south to Eugene,
long-barren branches sprout white, 5-petaled blossoms tinged with pink.
Any other year,
our family would be taking weekend trips
southwest to Sherwood or east towards Hood River
to visit their orchards.
Like the Fuji and Gala varieties that predominate in this region,
most apple cultivars are self-unfruitful—
which is to say that they require cross-pollination
to produce a good crop consistently.
When fertilized by the pollen of Fuji apple blossoms
(or those of Braeburn, Honey Crisp, or McIntosh varieties),
a Gala apple tree can yield 20 kilograms of fruit each season.
Those Gala trees, in return, endow their pollen on the Fuji apple trees
so that they too may blossom and bear one or two bushels of fruit, each.
The Dance of the Honey Bee
Appletree pollen is sticky.
In contrast with the windborne pollen of Alder, Birch, and Ash trees
(whose allergenic quality gave the Willamette its name,
meaning “valley of sickness” in the indigenous Kalapuya dialect),
appletrees rely on insects to transfer pollen —
particularly the honey bee.
Honey bees eat the pollen of flowers and convert their nectar into honey.
Some of the pollen sticks to their furry bodies,
which is inadvertently spread as they move from plant to plant.
When a scout bee encounters a food source,
she flies back to the hive
and communicates the location of that food source to male worker bees
by performing what’s called a waggle dance.
Performed in darkness on the vertical honeycomb surface in the hive,
she’s able to convey the precise location of new food sources to them
by flying a coffee bean-shaped pattern oriented in the direction of the sun.
It’s an incredible feat,
made all the more remarkable by the fact that bees are not, individually,
very intelligent.
Bees have brains on the order of 1 million neurons,
compared to the 100 billion neurons of a human brain.
If you move a food source closer and farther away from a hive,
you can see how the dance changes to convey this new information.
But move it just past some critical point,
and the dance becomes something entirely different:
instead of the waggle dance,
the bee performs a round dance
with a totally different cadence and flight path.
For many years,
the dance language of the bumblebee eluded all those who studied it.
That is until
a mathematician named Barbara Shipman
made the connection between a bee’s dance language
and the six-dimensional geometry of flag manifolds,
of all things.
What was the unique insight that allowed her to see what others couldn’t?
She grew up in a family of beekeepers
and cultivated an interest in mathematics and biology
that carried throughout her studies.
The leap from furry, buzzing insects to abstract geometry is inconceivable
unless you’re accustomed to looking at the world in that particular way.
The Rose that Grows From the Dunghill
When Apple first announced the Swift programming language in 2014,
it generated a flurry of excitement as we all tried to understand it.
One of the most powerful tools at our disposal for understanding is analogy:
New Thing is like Familiar Thing crossed with Another Thing.
So in those early days,
there was a lot of discussion within the community
attempting to compare and contrast Swift withHaskell or Go or Python or Scheme or Dylan.
Last year,
we saw something similar with at WWDC 2019.
Anyone familiar with React or Elm
immediately recognized their influence onSwiftUI and Combine
(even if Apple hadn’t come out and acknowledged it explicitly).
For some,
the connection between React and Elm with JavaScript
is an inconvenient truth.
I’ve seen numerous developers profess their disdain for the language
in ways that echo the old rivalry between iOS and Android
(or the even older rivalry between Mac and PC).
And yet,
there are countless examples of good ideas from “them”
being criticized and mocked until they’re incorporated into an Apple product:
Why did we consider these good ideas heretical until Apple did it?
Us vs. Them
Another flavor of this arises from the dichotomy between “Native” and “Hybrid”.
Whenever a company writes some blog post about React Native,
what inevitably follows is chorus of developers who either
praise the decision as courageous (if switching away)
or call it idiotic (if adopting it).
As developers,
we tend to align ourselves with enlightenment ideals like objectivity.
We say that we make decisions based in the indisputable reality of fact.
We consider ourselves reasonable and our decisions well-reasoned.
But to what extent is this actually true?
Do our thoughts lead us to our preferences,
or do we use thoughts to rationalize them after the fact?
In the 1960s and 70s,
the social psychologist Henri Tajfel and his colleagues
ran a series of experiments
that demonstrated how little it takes
for people to engage in intergroup discrimination.
In one experiment,
a group of boys were shown pictures with clusters of dots
and instructed to guess how many there were
as a test of their visual judgment.
The researchers split the group between
those who overestimated or underestimated the number.
Except, they only pretended to do this —
the boys were, in fact, randomly assigned to one of the two groups.
They were then given the task of allocating a fixed amount of real money
to other boys in the study.
The results surprised even the researchers:
Overwhelmingly, the boys chose outcomes where their assigned group
(under- or over-estimators) received more money than their counterparts —
even when that meant getting less overall.
Successful replication of these results in follow-up studies since then
presents compelling evidence of this peculiarity in human nature.
That a willingness to engage in “us vs. them” discrimination
can arise from completely artificial distinctions,
irrespective of any rationale of self-interest.
How else could you explain the intense tribalism
around how we talk to computers?
The Dream of Purity
When a developer proudly declares something to be“Pure Swift” or “100% JavaScript free”,
what are they really saying?
What’s presented as an objective statement of fact
often feels more like an oath of allegiance.
If you see the existence of competing technologies
as a fight between good and evil,
perhaps there are more important battles to fight.
If you can’t evaluate solutions as a series of trade-offs,
what chance do you have at accomplishing anything at all?
Yes,
there are real differences between technologies
and reasonable people disagree about
which one is best-suited to solve a particular problem.
But don’t mistake this for a moral contest.
Purity is an ideal;
a vision of the condition which needs yet to be created,
or such as needs to be diligently protected against the genuine or imagined odds.
Without such a vision, neither the concept of purity makes sense,
nor the distinction between purity and impurity can be sensibly drawn.
– Zygmunt Bauman
It’s of no practical consequence that
the grounds on which Apple Park sits today
were fruit orchards a hundred years ago.
But it’s poetic.
Long before it was “Silicon Valley”,
the stretch of land between the San Andreas and Hayward faults
was called “the Valley of Heart’s Delight”
for all of its fruit trees and flowering plants.
Dwelling on this,
you might reflect on how humans are like apple trees.
That we need a variety of different influences to reach our potential.
(Even self-starters benefit from a unique perspective).
You might then consider what we share in common with
the bees that pollinate apple trees.
Like them,
our success comes not from our individual intelligence,
but in our ability to share information.
Whether we’re like bees or like apples,
we come away learning the same lesson:
We can achieve remarkable results by working together.
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.
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.
importCoreBluetooth// Contact Detection service UUIDletserviceUUID=CBUUID(string:"FD6F")// Rolling Proximity Identifierletidentifier:Data=…// 16 bytesletperipheralManager=CBPeripheralManager()letadvertisementData:[String:Any]=[CBAdvertisementDataServiceUUIDsKey:[serviceUUID]CBAdvertisementDataServiceDataKey:identifier]peripheralManager.startAdvertising(advertisementData)
At the same time that the device broadcasts as a peripheral,
it’s also scanning for other devices’ Rolling Proximity Identifiers.
Again, here’s how you might do that on iOS using Core Bluetooth:
Bluetooth is an almost ideal technology for contact tracing.
It’s on every consumer smart phone.
It operates with low power requirement,
which lets it run continuously without draining your battery.
And it just so happens to have a transmission range
that approximates the physical proximity required
for the airborne transmission of infectious disease.
This last quality is what allows contact tracing to be done
without resorting to location data.
Software
Your device stores any Rolling Proximity Identifiers it discovers,
and periodically checks them against
a list of Positive Diagnosis Keys sent from the central health authority.
Each Positive Diagnosis Key corresponds to someone else’s 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
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.
Visual Studio Code (VSCode)
is a cross-platform text and source code editor from Microsoft.
It’s one of the most exciting open source projects today,
with regular updates from hundreds of contributors.
VSCode was among the first tools to support
Language Server Protocol (LSP),
which has played a large part in providing a great developer experience,
in a variety of languages and technologies.
This week,
we’ll walk through the process of how to get started with
Swift’s new Language Server Protocol support in Visual Studio Code on macOS.
If you haven’t tried writing Swift outside Xcode,
or are already a VSCode user and new to the language entirely,
this article will tell you everything you need to know.
Step 0: Install Xcode
If you don’t already have Xcode installed on your machine,
open the Terminal app and run the following command:
$xcode-select --install
Running this command presents a system prompt.
Click the “Get Xcode” button
and continue installation on the App Store.
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).
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.
At Apple we are making it a priority
to support high-quality tooling for all Swift developers,
including those working on non-Apple platforms.
We want to collaborate with the open-source community
and focus our efforts on building common infrastructure
that can be shared by Xcode and other editors and platforms.
To that end, [ … ] we’ve chosen to adopt LSP.
Argyrios Kyrtzidis, October 15th, 2018
This is arguably the most important decision Apple has made for Swift
since releasing the language as open source in 2014.
It’s a big deal for app developers,
and it’s an even bigger deal for Swift developers on other platforms.
To understand why,
this week’s article will take a look at
what problem the Language Server Protocol solves,
how it works,
and what its long-term impacts may be.
Imagine a grid
with each row representing a different programming language
(Swift, JavaScript, Ruby, Python, etc.)
and each column representing a different code editor
(Xcode, Visual Studio, Vim, Atom, etc.),
such that each cell represents
the level of support that a particular editor has for a language.
Up until recently,
what you’d find was a patchwork of compatibility
across the various combinations.
Some editors offered deep integration with a few languages
and little to no support for anything else,
whereas other editors aimed to be general-purpose
with at least a modicum of support for many languages.
(The term IDE
is often used to describe the former.)
Case in point:You’d be stubborn not to use Xcode for app development
and foolish to use it for anything else.
For an editor to have better support for a particular language,
it needs to write integration code —
either directly in the code base or via a plugin system.
Due to implementation differences across languages and editors,
improvements to, say,
Ruby support in Vim
wouldn’t translate into better support for Python,
nor could they be applied to make Ruby work better in Atom.
The end result: inconsistent support across technologies
and a lot of wasted effort.
The situation we described
is often referred to as an M × N problem,
where the number of integrations is the product ofM editors and N languages.
What the Language Server Protocol does is change this M × N problem
into a M + N problem.
Rather than an editor having to implement support for each language,
it only needs to support the LSP.
And in doing so, it gets the same level of functionality
for all languages that support the LSP.
Language Server Protocol provides a common set of functionality
for supported languages, including:
Syntax Highlighting
Automatic Formatting
Autocomplete
Syntax
Tooltips
Inline Diagnostics
Jump to Definition
Find References in Project
Advanced Text and Symbol Search
Rather than reinventing the wheel for each new technology,
tools and editors can invest in better usability
and more advanced functionality.
How Language Server Protocol Works
If you’re an iOS developer,
you may be most familiar with
the terms server and protocol
in the sense of communicating with web applications
in JSON format via HTTP.
This actually isn’t too far off from how the Language Server Protocol works.
In the case of LSP,
the client refers to the editor —
or more generally, the tool —
and the server refers to
an external program run locally in a separate process.
As for the protocol itself,
LSP resembles a simplified version of HTTP:
Each message consists of
a header part and a content part.
The header part has a required Content-Length field
containing the size of the content part in bytes,
and an optional Content-Type field
(application/vscode-jsonrpc; charset=utf-8 by default)
The content part uses JSON-RPC
to describe the structure of requests, responses, and notifications.
Whenever something happens in the tool,
such as the user jumping to the definition of a symbol,
the tool sends a request to the server.
The server receives that request
and then returns an appropriate response.
For example,
imagine that a user opens the following Swift code
in an Xcode-like editor that supported the Language Server Protocol:
classParent{}classChild:Parent{}
When the user ⌘-clicks the symbol Parent
in the inheritance clause on line 2,
the editor jumps to the definition of the Parent class on line 1.
Here’s how LSP enables this interaction behind the scenes:
First,
when the user opens the Swift code,
the editor launches its Swift language server in a separate process,
if it isn’t running already,
and performs any additional setup.
When the user executes the “jump to definition” command,
the editor sends the following request to its Swift language server:
Upon receiving this request,
the Swift language server uses a compiler tool likeSourceKit
to identify the corresponding code entity
and find the location of its declaration on the preceding line.
The language server then responds with the following message:
Finally,
the editor navigates to the file
(which, in this case, is already open),
moves the cursor to that range,
and highlights the token.
The beauty of this approach is that the editor did all of this
without knowing anything about the Swift programming language
other than that .swift files are associated with Swift code.
All the editor needs to do
is talk to the language server and update the UI.
And knowing how to do that,
the editor can follow the same procedure
to facilitate this interaction
for code written in any language with a language server implementation.
Language Server Protocol Support in Clang / LLVM
If the M + N diagram from before looks familiar,
it might be because it’s the same approach taken by LLVM.
At the core of LLVM is an intermediate representation (IR).
Supported languages generate IR using a compiler frontend,
and that IR can generate machine code
for any platform supported by a compiler backend.
The LLVM compiler frontend for C languages
is called Clang.
It’s also used by Swift for inter-operability with Objective-C.
In its recent 5.0.0 release,
Clang added a new tool called
Clangd,
LLVM’s implementation for the Language Server Protocol.
Now you might think, “So what?”
Apple is among the most prominent supporters of the LLVM project —
having, among other things,
employed the project’s founder, Chris Lattner, for over a decade.
Apple’s decision to switch from one obscure Clang tool to another
would seem to be an implementation detail (so to speak).
What makes this announcement quite interesting
is that Clangd appears to have been created entirely outside of Apple,
with significant contributions from Google and other companies.
This announcement signals a significant shift
in the direction of tooling development going forward —
something that would be confirmed 6 months later on the Swift.org forums.
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:
Potential Consequences of Apple’s Support of Language Server Protocol
It’ll take some time to feel the full impact of these developments,
but believe me: your patience will be rewarded.
Here are just a few of what I believe will happen as a result of LSP
in the coming months and years.
Swift Becomes More Appealing as a General-Purpose Programming Language
Although Swift is used primarily for app development,
it was designed from the start to be
a capable general-purpose programming language.
BetweenSwift for TensorFlow,SwiftNIO,
and other projects,
we’re just starting to see the promise of
what Swift can do beyond the App Store.
Among the biggest factors holding Swift back from
mainstream adoption up to this point
has been its reliance on Xcode.
It’s a lot to ask, say, a web developer or machine learning engineer
to download Xcode just to try Swift
when there are so many great alternatives
with a much lower barrier to entry.
Support for the Language Server Protocol
should make it significantly easier for folks outside the Apple ecosystem
to evaluate Swift with the same, familiar tools they use for everything else.
Xcode Gets Better
Adopting LSP isn’t just about making Swift work better in other editors;
Xcode stands to benefit immensely, as well.
Consider this forum post
from Project Lead for Swift at Apple, Ted Kremenek:
The LSP service [Argyrios] is describing
will be functionally more powerful than SourceKit is today.
LSP is an opportunity for the Xcode team to take a fresh approach to
Swift integration,
and to capitalize on all of the improvements to the language and tooling
in the four years since its 1.0 release.
Our first glimpse into this overhauled infrastructure comes by way ofIndexStoreDB:
a powerful new API for querying code symbols in Swift projects
from a Clang index.
Getting Xcode to use our new LSP service
should make it viable to use other LSP services as well,
and it’s something that we are interested in,
but we don’t have specific plans to announce at this moment.
The main focus for the current efforts are to improve the story for Swift.
But once that’s done, it should be relatively straightforward
to have those benefits cascade down to other languages with LSP support.
The architecture of software
reflects the structure and values of the organizations that create it.
The converse is true as well, to some extent.
By adopting the open Language Server Protocol standard for Xcode,
Apple is making good on its commitment to the success of Swift
on platforms outside the Apple ecosystem.
And I think it’ll work:
tooling (or lack thereof) is often the key decider
in which technologies gain mindshare.
But perhaps more importantly,
I believe this decision demonstrates an increased willingness within
(at least some small part of) the company
for collaboration and transparency.
Like everything else in 2020,
this year’s WWDC had to be a little different
if it was going to happen at all.
When Apple first announced that the conference would be fully remote,
nobody knew what that would look like, exactly.
What parts of the dubdub experience would be kept in this new format?
What details would be lost in translation?
Could they actually pull it off?
As it turns out,
going fully remote wasn’t merely good enough —
it was, in many ways, superior to the original thing.
There’s a lot to like about the new format.
The videos are well-produced,
and let each presenter’s personality really shine.
Everybody looks and sounds great.
Sessions are tight and well-paced.
Rather than stretching or cramming content into a fixed time slot,
they’re as long as they need to be.
And thanks to this more digestible format,
we’re starting to see WWDC clips being shared around,
which is new and refreshing.
To be honest,
it’s hard to imagine ever going back to a physical conference.
However,
as someone who had the privilege of attending WWDC in years past,
there are things I’m going to miss
(and some that I decidedly won’t).
🥰
😫
Refrigerators stocked with Odwalla smoothies
Trying to download the latest Xcode beta over hotel internet
Lunchtime sessions
Eating lunch at or around Moscone
WWDC track jackets saving the lives of first-time attendees from the cold of San Francisco summer
Being in San Francisco, generally
Eating burritos on the terrace of McEnery Convention Center during WWDC check-in
Being in San Jose, generally
Guessing who would be playing at Bash this year
Hearing the same handful of songs on repeat before and after every session
(this song in particular)
Watching Apple executives dance at Bash
Talking to people as you wait in line for the keynote on Monday morning
Waking up late and having to settle for watching from the overflow room
Leaving at the end of the week with a mix of hope, fear, and inspiration *
Being sick for the next week with dubdub flu
* I’d like to hold on this last point for a moment.
In the Before Times,
many of us traveled far from home to attend WWDC.
There was a physical and temporal delineation
between life before, during, and after the conference.
Flying out of SFO, SJC, or OAK,
you escaped Apple’s “reality distortion field”
and its surrounding echo chamber.
You returned to your normal life.
This year? Not so much.
WWDC 2020 was just another week in this bizarre existence amidst this pandemic.
Not only is there no escape from the “reality distortion field”,
there isn’t even a “normal life” for us to leave or return to.
So here we are,
filled with anxious excitement and fear;
our corporeal forms replaced by
Memoji floating in a black, digital void
lit only by the screens of our soon-to-be-obsolete MacBooks Pro.
Excitement
I don’t have a real sense of how everything went over this year.
There wasn’t any applause (or heckling) at this year’s keynote
to gauge the temperature in the room.
There were no parties to overhear shop talk and hot takes.
There was no line for lunch
to make small talk with a fellow attendee.
But if Twitter is anything to go on,
my overall impression is that everyone is really excited.
Which is fine. I get it.
But it should come as no surprise that things announced at WWDC are exciting —
that’s the whole point of having a developer conference in the first place.
Apple has the best marketing in the world,
and WWDC is Apple’s way of marketing to us.
Here’s the thing about excitement:
It’s kryptonite to developers.
Excitement messes with our ability to focus on one thing,
which is already a big struggle for a lot of us (myself included).
When you’re excited,
it’s almost impossible to get anything done.
There are plenty of voices in the community who are echoing this excitement.
I can’t add anything to that discussion.
And besides,
that’s not really where my head’s at right now.
Trivial Pursuit
I briefly considered reviving the NSHipster Quiz
for WWDC 2020,
but it didn’t feel right.
With everything going on in the world,
Apple trivia just isn’t where my head or heart are right now.
To give you a sense of what I mean, here’s what I had for Round 3,
whose theme was inspired by Richard Hamming:
Question 1.
What are the important problems of your field?
Question 2.
What important problems are you working on?
Question 3.
If what you are doing is not important,
why are working on it?
Temperance
If you’re serious about solving a problem,
you owe it to yourself to temper any excitement
that distracts you from making real progress.
In last year’s write-up for WWDC 2019,
I concluded with the following,
as a counterpoint to the conference’s theme of #mindblown🤯:
Taking care of yourself —
sleeping enough, eating right, exercising regularly —
will do more to improve your productivity
than any language or framework out there.
Your ability to communicate and collaborate with others
will always be a better predictor of success
than your choice of technology stack.
Your relationships with others
are the most significant factors of success and happiness in life.
I stand by this advice, boring as it may be.
It’s been an exciting week,
so take a moment to collect yourself.
Go on a walk. Take a hike. (Be safe about it.)
Do whatever you need to break free of the “reality distortion field”.
Once you do, you’ll have the necessary distance to determine
what new technologies you should pay attention to
and what you can ignore for now.
Chris Lattner often describes LLVM as a process of lowering.
You start at the highest level of abstraction,
source code written in a programming language like Swift or Objective-C.
That code is parsed into an abstract syntax tree,
(AST),
which is progressively transformed into
lower-level, intermediate representations
until it finally becomes executable binary.
What if,
instead of lowering source code down for the purpose of execution,
we raised source code for the purpose of understanding?
In this article,
I’d like to share an idea that I’ve been kicking around for a while.
It’s something that’s come into greater focus with
my recent work on swift-doc,
but first started to form during tenure in Apple Developer Publications,
back in 2015.
The idea is this: What if we took the lessons of the semantic web
and applied them to source code?
Specifically:
Representation:
Software components should be represented by
a common, language-agnostic data format.
Addressability:
Packages, modules, and their constituent APIs
should each have a unique URL identifier.
Decentralization:
Information should be distributed across a federated network of data sources,
which can cross-reference one another by URL.
I grew up with the Internet,
and got to see it, first-hand,
go from an obscure technology to the dominant cultural force.
So much of what I see in software development today
reminds me of what I remember about the web from 20 years ago.
And if you’ll forgive the extended wind-up,
I think there’s a lot we can learn by looking at that evolution.
Web 1.0 The Web of Documents
Tim Berners-Lee launched the World Wide Web
from a NeXT workstation 27 years ago.
His vision for a
globally-distributed, decentralized network of inter-connected documents
gave rise to the Internet as we know it today.
But it was also part of an intellectual tradition dating back to the 1940s,
which includes
Vannevar Bush’s Memex,
Ted Nelson’s Xanadu, and
Doug Engelbart’s Mother of All Demos.
In those early days,
the knowledge being shared was primarily academic.
As the userbase grew over time,
so too did the breadth and diversity of the information available.
And, for a time,
that’s what the Internet was:
fan sites for Sunbeam toasters,recipes for Neapolitan-style pizza, andthe official website for the 1996 film Space Jam.
But the web of documents had limits.
If you wanted to
shop for appliances,
see the menu of a pizza shop, or
get local showtimes for a movie,
you might be able to do that on the early Internet.
But you really had to work at it.
Back then,
you’d start by going to a directory like Yahoo! or DMOZ,
navigate to the relevant topic,
and click around until you found a promising lead.
Most of the time, you wouldn’t find what you were looking for;
instead, you’d disconnect your modem to free up your landline
and consult the yellow pages.
This started to change in the early ’00s.
Web 2.0 The Social Web
With Perl CGI and PHP,
you could now easily generate web pages on-the-fly.
This enabled eCommerce and the first commercial uses of the Internet.
After the ensuing dot-com bubble,
you had technologies like Java applets and Flash
bring a new level of interactivity to web sites.
Eventually, folks figured out how to usean obscure API from Internet Explorer 5
to replicate this interactivity on normal webpages —
a technique dubbed AJAX.
Interacting with a page and seeing results live, without reloading a page?
This was huge.
Without that,
social media might not have taken off as it did.
Anyway,
the server-side APIs powering those AJAX interactions on the client,
they were the secret sauce that let the Internet evolve into what it is today.
Thanks to all of these (often unsecured) AJAX endpoints,
developers could synthesize information across multiple sources
in ways that nobody had ever thought to do.
You could get someone’s location from Fire Eagle,
search for photos taken nearby on Flickr,
and use MOO to print and deliver prints of them on-demand.
By the end of the decade,
the rise of social networks and the early promise of mashups
started to coalesce into the modern Internet.
Web 3.0 The Web of Data
The term “Web 3.0” didn’t catch on like its predecessor,
but there’s a clear delineation between
the technologies and culture of the web between the early and late ’00s.
It’s hard to overstate how much the iPhone’s launch in 2007
totally changed the trajectory of the Internet.
But many other events played an important role in
shaping the web as we know it today:
Google acquiring the company behind Freebase,
giving it a knowledge graph to augment its website index.
Facebook launching Open Graph,
which meant everything could now be “Liked”
(and everyone could be targeted for advertisements).
Yahoo releasing SearchMonkey andBOSS,
two ambitious (albeit flawed) attempts
to carve out a niche from Google’s monopoly on search.
Wolfram launching Wolfram|Alpha,
which far exceeded what many of us thought was possible
for a question answering system.
The Internet always had a lot of information on it;
the difference now is that
the information is accessible to machines as well as humans.
Today,
you can ask Google“Who was the first person to land on the moon?”
and get an info box saying, “Commander Neil Armstrong”.
You can post a link in Messages
and see it represented bya rich visual summary
instead of a plain text URL.
You can ask Siri,“What is the airspeed velocity of an unladen swallow?” and hear back“I can’t get the answer to that on HomePod”About 25 miles per hour.
Think about what we take for granted about the Internet now,
and try to imagine doing that on the web when it lookedlike this.
It’s hard to think that any of this would be possible without the semantic web.
GitHub.com, Present Day The Spider and The Octocat
READMEs on GitHub.com today remind me of
personal home pages on Geocities back in the Web 1.0 days.
Even with the standard coat of paint,
you see an enormous degree of variance across projects and communities.
Some are sparse; others are replete with adornment.
And yet,
no matter what a project’s README looks like,
onboarding onto a new tool or library entails, well reading.
GitHub offers some structured informational cues:
language breakdown, license, some metadata about commit activity.
You can search within the repo using text terms.
And thanks to semantic / tree-sitter,
you can even click through to find declarations in some languages.
But where’s a list of methods?Where are the platform requirements?
You have to read the README to find out!
(Better hope it’s up-to-date 😭)
The modest capabilities of browsing and searching code today
more closely resemble AltaVista circa 2000 than Google circa 2020.
Theres so much more that we could be doing.
RDF Vocabularies The Owl and The Turtle
At the center of the semantic web is something calledRDF,
the Resource Description Framework.
It’s a collection of standards for representing and exchanging data.
The atomic data entity in RDF
is called a triple, which comprises:
a subject (“the sky”)
a predicate (“has the color”)
an object (“blue”)
You can organize triples according to avocabulary, or ontology,
which defines rules about how things are described.
RDF vocabularies are represented by the
Web Ontology Language
(OWL).
The ideas behind RDF are simple enough.
Often, the hardest part is navigating
its confusing, acronym-laden technology stack.
The important thing to keep in mind is that
information can be represented in several different ways
without changing the meaning of that information.
Let’s start to define a vocabulary for the Swift programming language.
To start,
we’ll define the concept of a
Symbol along with two subclasses, Structure and Function.
We’ll also define a name property that holds a token (a string)
that applies to any Symbol.
Finally,
we’ll define a returns property that applies to a Function
and holds a reference to another Symbol.
Encoding our knowledge into a standard format
lets anyone access that information — however they like.
And because these facts are encoded within an ontology,
they can be validated for coherence and consistency.
It’s totally language agnostic.
Querying the Results
With an RDF graph of facts,
we can query it using SPARQL.
Or,
we could load the information into
a graph database like Neo4j or
a relational database like PostgreSQL
and perform the query in Cypher or SQL, respectively.
“What can you do with a knowledge graph?”
That’s kind of like asking, “What can you do with Swift?”
The answer — “Pretty much anything”— is as true as it is unhelpful.
Perhaps a better framing would be to consider the kinds of questions that
a knowledge graph of code symbols can help answer:
Which methods in Foundation produce a Date value?
Which public types in my project don’t conform to Codable?
Which methods does Array inherit default implementations from RandomAccessCollection?
Which APIs have documentation that includes example code?
What are the most important APIs in MapKit?
Are there any unused APIs in my project?
What’s the oldest version of iOS that my app could target
based on my current API usage?
What APIs were added to Alamofire between versions 4.0 and 4.2?
What APIs in our app are affected by a CVE issued for a 3rd-party dependency?
The possibilities get even more interesting as you layer additional contexts
by linking Swift APIs to different domains and other programming languages:
How is this Swift API exposed in Objective-C?
Who are the developers maintaining the packages
that are pulled in as external dependencies for this project?
What’s the closest functional equivalent to this Swift package
that’s written in Rust?
Future Applications The Promise of What Lies Ahead
Any fact becomes important when it’s connected to another.
Umberto Eco, Foucault’s Pendulum
Operating on code symbolically is more powerful
than treating it as text.
Once you’ve experienced proper refactoring tools,
you’ll never want to go back to global find-and-replace.
The leap from symbolic to semantic understanding of code
promises to be just as powerful.
What follows are a few examples of potential applications of
the knowledge graph we’ve described.
Flexible Search Queries
GitHub’s advanced search
provides an interface to filter results on variousfacets,
but they’re limited to metadata about the projects.
You can search for Swift code written by
@kateinoigakukun in 2020,
but you can’t, for example,
filter for code compatible with Swift 5.1.
You can search code for the string “record”,
but you can’t disambiguate between type and function definitions
(class Record vs. func record()).
As we showed earlier,
the kinds of queries we can perform across a knowledge graph
are fundamentally different from what’s possible with
a conventional faceted, full-text search index.
For example,
here’s a SPARQL query to find the urls of repositories
created by @kateinoigakukun and updated this year
that contain Swift functions named record:
When faced withmissing or incomplete documentation,
developers are left to search Google for
blog posts, tutorials, conference videos, and sample code
to fill in the gaps.
Often, this means sifting through pages of irrelevant results —
to say nothing of outdated and incorrect information.
A knowledge graph can improve search for documentation
much the same as it can for code,
but we can go even further.
Similar to how academic papers contain citations,
example code can be annotated to include references to
the canonical APIs it interacts with.
Strong connections between references and its source material
make for easy retrieval later on.
Imagine if,
when you option-click on an API in Xcode
to get its documentation,
you also saw a list of sample code and WWDC session videos?
Or what if we could generate sample code automatically from test cases?
Wouldn’t that be nice?
All of that information is out there,
just waiting for us to connect the dots.
Automatic µDependencies
John D. Cook onceobserved,
code reuse is more like an organ transplant
than snapping LEGO blocks together.
Fred Brooks similarly analogized software developers to surgeons inThe Mythical Man-Month.
But that’s not to say that things can’t get better —
it’d be hard to argue that they haven’t.
Web applications were once described in similar, organic terms,
but that came to an end with the advent ofcontainerization.
Now you can orchestrate entire multi-cloud deployments automatically
via declarative configuration files.
Before CPAN,
the state of the art for dependency management
was copy-pasting chunks of codeyou found on a web page.
But today, package managers are essential infrastructure for projects.
What if,
instead of organizing code into self-contained, modular chunks ourselves,
we let software do it for us?
Call itFaaD (Functions as a Dependency).
Say you want an implementation ofk-means clustering.
You might search around for “k-means” or “clustering” on GitHub
and find a package named “SwiftyClusterAlgorithms” (😒),
only to discover that it includes a bunch of functionality that you don’t need —
and to add insult to injury,
some of those extra bits happen to generate compiler warnings.
Super annoying.
Today, there’s no automatic way to pick and choose what you need.
(Swift import syntax (import func kMeans) is a lie)
But there’s no inherent reason why the compiler couldn’t do this for you.
Or to go even further:
If everything compiles down to web assembly,
there’s no inherent requirement for that implementation of k-means —
it could be written in Rust or JavaScript,
and you’d be none the wiser.
At a certain point,
you start to question the inherent necessity of software packaging
as we know it today.
Take it far enough,
and you may wonder how much code we’ll write ourselves in the future.
Code Generation
A few months ago,
Microsoft hosted its Build conference.
And among the videos presented was an interview withSam Altman,
CEO of OpenAI.
A few minutes in,
the interview cut to a video of Sam using
a fine-tuned version ofGPT-2
towrite Python code from docstrings.
defis_palindrome(s):"""Check whether a string is a palindrome"""returns==s[::-1]# ← Generated by AI model from docstring!
And that’s using a model that treats code as text.
Imagine how far you could go with a priori knowledge of programming languages!
Unlike English, the rules of code are, well, codified.
You can check to see if code compiles —
and if it does compile,
you can run it to see the results.
At this point,
you should feel either very worried or very excited.
If you don’t, then you’re not paying attention.
Taking Ideas Seriously The Shoemaker’s Children
The use of FORTRAN,
like the earlier symbolic programming,
was very slow to be taken up by the professionals.
And this is typical of almost all professional groups.
Doctors clearly do not follow the advice they give to others,
and they also have a high proportion of drug addicts.
Lawyers often do not leave decent wills when they die.
Almost all professionals are slow to use their own expertise for their own work.
The situation is nicely summarized by the old saying,“The shoe maker’s children go without shoes”.
Consider how in the future, when you are a great expert,
you will avoid this typical error!
Today,
lawyers delegate many paralegal tasks like document discovery to computers
and
doctors routinely use machine learning models to help diagnose patients.
So why aren’t we —
ostensibly the people writing software—
doing more with AI in our day-to-day?
Why are things like
TabNine andKite
so often seen as curiosities instead of game-changers?
If you take seriously the idea thatAI
will fundamentally change the nature of many occupations in the coming decade,
what reason do you have to believe that you’ll be immune from that
because you work in software?
Looking at the code you’ve been paid to write over the past few years,
how much of that can you honestly say is truly novel?
We’re really not as clever as we think we are.
Postscript Reflection and Metaprogramming
Today marks 8 years since I started NSHipster.
You might’ve noticed that I don’t write here as much as I once did.
And on the occasions that I do publish an article,
it’s more likely to include obscure
historical facts andcultural references
than to the obscure APIs promised by this blog’s tagline.
The truth is,
I’ve come around to thinking that
programming isn’t the most important thing
for programmers to pay attention to right now.
Anyway,
I’d like to take this opportunity to extend my sincere gratitude
to everyone who reads the words I write.
Thank you.
It may be a while before I get back into a regular cadence,
so apologies in advance.
Until next time,May your code continue to compile and inspire.
.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.
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:
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.
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
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.
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.
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.
“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.
$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:
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:
Converting documents into embeddings
Finding relevant documents based on the query
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.
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.
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:
They don’t know anything outside their training data
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>
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,"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:
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.
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!
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.
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✌️