Sam Symons

Exploring Swift, Part 2: Installing Custom Toolchains

As you’re making changes to Swift, you’ll want to test them out by using your new version of the compiler and writing some programs with it. The most convention way to do this is to provide the system with a new version of Swift to use.

Some of the projects in the Swift ecosystem (like the package manager) may even require that you have a more recent version than that which Xcode provides. As Swift 3.0 evolves, the other projects evolve with it, and these changes can happen faster than Xcode betas are released.

What Are Toolchains Anyway?

In recent versions of Xcode, you may have seen mentions of toolchains. You might have even seen people talking about the TOOLCHAINS environment variable. It’s important to know what this is all about, since toolchains are the best way to use your own homegrown version of Swift.

A toolchain is little more than a collection of binaries. It contains the Swift compiler, LLDB, along with a few other tools and an Info.plist with some information about itself. It’s a way of containing everything you need to build a Swift project in a single directory.

The way Xcode manages these is by putting them all into Library/Developer/Toolchains and then pointing the swift-latest.xctoolchain symlink (in the same directory) at whichever one is currently active. It also uses this list to populate the Toolchains screen in Xcode’s preferences.

Building Swift

As a toolchain is just a directory with some Swift binaries in it, building a toolchain of your very own isn’t too scary. To kick things off, cd into your Swift directory and bring the project up to date:

./utils/update-build --clone-with-ssh

Before building, make sure you xcode-select to your Xcode-beta.app path. The build process looks for the 10.12 SDK, which is bundled with Xcode 8. Without this, you won’t be able to build!

You should be set to build Swift now.

./utils/build-script -R --llbuild --swiftpm

Creating a toolchain is pretty straightforward. I found a great mailing list thread linking to this build script from Daniel Dunbar. As Daniel mentioned, this script was written for his personal use; I created a modified version which can be easily updated for any system.

This script can be run after compilation to build a swift-dev.toolchain directory.

Using Custom Toolchains

With your custom swift-dev toolchain in hand (it will be in the same build directory as Swift, Ninja-Release), head over to Libary/Developer/Toolchains and drop it in there. Now you can tell xcrun which version of Swift to use.

export TOOLCHAINS=swift-dev

After setting the TOOLCHAINS environment variable, the system will look in Libary/Developer/Toolchains for a corresponding version of Swift instead of using the toolchains that come packaged with Xcode. Now you’re ready to use your new compiler!

› swift --version
Swift version 3.0-dev (LLVM 505155ac3d, Clang f8606ef4b8, Swift b4cba58330)
Target: x86_64-apple-macosx10.9

Perfect! The swift binary prints the SHA for each dependency its using, so this should line up with the latest commit you built from.

Creating Siri Extensions With SiriKit

With the introduction of iOS 10 at WWDC 2016, Siri has finally been opened up to developers, in the form of SiriKit.It comes with support for a fixed set of app categories for now, ranging from messaging and phone calls to payments and booking rides.

Siri is an extremely complicated product, having to manage many languages and process words correctly, so I wanted to see how easy it is to add support for it to an app.

I’m going to walk through the entire process of building a basic payments app, covering Intents, the Intents user interface, and the overall SiriKit integration itself.

Note: Because Siri does not yet run in the simulator, this sample code will only work on a device running iOS 10.

Defining the Payments App

Before getting started, I want to go over how the app will be structured. The only two concepts I’m interested in are contacts, the people to whom we will be sending payments, and the payments themselves. This demo won’t persist payments to disk, but you’ll be able to see where that work would be done.

SiriKit integrates nicely with the contacts framework to retrieve the names of people we want to send contacts to, but just to mix it up, the app will provide its own contacts through INPerson.

Lastly, I will build a custom UI for our interactions with Siri using the IntentsUI framework. I want users to see custom branding and designs for the app, rather than using the generic payments template seen in iOS 10, and Apple makes this easy to do.

If you’d like to follow along, create a new project in Xcode 8 using the single view controller template.

Laying the Foundations

The Siri extensions are kept separate from the core classes of your app, so a good way to share functionality between them is to set up a framework. Head over to the project navigator and click the + in the Targets list. You’ll want to create a Cocoa Touch Framework here – I’ve named it PaymentsCore.

Add a new Swift file to the framework named PaymentsContact. This is how the app will represent its contacts, using these classes to expose our user data to Siri.

Here’s my declaration of PaymentsContact:

public class PaymentsContact {
  public let name: String
  public let emailAddress: String

  public init(name: String, emailAddress: String) {
    self.name = name
    self.emailAddress = emailAddress
  }
}

Note that because a framework is used here, you have to concern yourself with access control. The class and its user-facing functions must be declared as public.

I’ll extend PaymentsCore later, but this is enough to get started. Inside the main view controller class, add import PaymentsCore and create a new contact object to verify that the framework is set up correctly. If you’re able to access the framework classes from here, using them in SiriKit won’t be a problem.

Handling Basic Intents

Siri interacts with apps through the Intents and Intents UI extensions. At the bare minimum, you need to provide an implementation of the first one.

Intents is a fairly simple framework. It consists of three main pieces:

  1. An Info.plist file, telling Siri where to find your extension handler
  2. A subclass of INExtension, which verifies that the app is capable of handling a given extension
  3. The payments intent handler itself, which takes data from Siri, performs actions with that data, and then tells Siri the results

Head back to the Targets list and click the add button again. In here, you should be able to find Intents Extension in the application extensions section. Add this, giving it a good name (PaymentsIntentExtension for me), though be sure to uncheck the checkbox asking to create a UI extension alongside this one. Those aren’t worth worrying about for now.

This adds a new Intents extension to the project, handling workout intents. I want to handle payment intents, so the first place to look is Info.plist. In here, remove the workout intent rows under the NSExtension key, and replace them with INSendPaymentIntent. This is the end result, copied straight from the .plist.

<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>IntentsRestrictedWhileLocked</key>
        <array>
            <string>INSendPaymentIntent</string>
        </array>
        <key>IntentsSupported</key>
        <array>
            <string>INSendPaymentIntent</string>
        </array>
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.intents-service</string>
    <key>NSExtensionPrincipalClass</key>
    <string>$(PRODUCT_MODULE_NAME).IntentHandler</string>
</dict>

This tells Siri that we want to handle the INSendPaymentIntent, that it shouldn’t be available while the phone is locked, and that IntentHandler is the class to talk to when asking to handle the payment intent.

Inside IntentHandler, I added a short INExtension subclass which just checks for the intent type:

import Intents

class IntentHandler: INExtension {
  override func handler(for intent: INIntent) -> AnyObject? {
    if intent is INSendPaymentIntent {
      return SendPaymentIntentHandler()
    }

    return nil
  }
}

SendPaymentIntentHandler is a custom class; add a new Swift file to the project, with this as its contents:

import Intents

class SendPaymentIntentHandler: NSObject, INSendPaymentIntentHandling {
  // MARK: - INSendPaymentIntentHandling

  func handle(sendPayment intent: INSendPaymentIntent, completion: (INSendPaymentIntentResponse) -> Swift.Void) {
    if let _ = intent.payee, let _ = intent.currencyAmount {
      // Handle the payment here!

      completion(INSendPaymentIntentResponse.init(code: .success, userActivity: nil))
    }
    else {
      completion(INSendPaymentIntentResponse.init(code: .success, userActivity: nil))
    }
  }
}

This isn’t really a functional extension yet, but it will actually run with Siri already. If you build the app on your device and say Send $100 to Tim Cook with Payments, Siri will attempt to run this through your extension… and fail. The very last piece of the puzzle is to get authorization for Payments to talk to Siri. The app needs to request it, so I’m doing it inside the ViewController class:

import Intents

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    INPreferences.requestSiriAuthorization() { (status) in
      print("New status: \(status)")
    }
  }
}

Run the app to get the authorization prompt, and then fire up Siri to send money to Tim Cook again. This time, you should get a success code back, telling the user that the money was successfully sent! To make it better, just saying Send $100 to Tim Cook will ask the user which app to use, presenting Payments in the list of options. Nice!

Debugging Extensions

Before diving into adding more functionality (including actual errors), it’s worth talking about extension debugging. Building and running your app does not allow you to hit breakpoints inside the extension; instead, you need to select PaymentsIntentExtension in the list of schemes. When you hit run, you’ll get a list of apps to open this extension with, where you’ll choose Siri.

Once this is running on your phone, launch Siri and give a query to process. If you have a breakpoint set in SendPaymentIntentHandler then this should be hit once the query has gone through!

Checking Contact Data with Siri

Right now, the app is returning a success code for every query. This isn’t very useful, so I’m going to create a canned list of contacts and register these with Siri. I’ll also provide two contacts with different email address but the same name, to demonstrate disambiguating requests.

I’ve added an allContacts function to PaymentsContact, like this:

public class func allContacts() -> [PaymentsContact] {
    return [
      PaymentsContact(name: "Tim Cook", emailAddress: "tim@apple.com"),
      PaymentsContact(name: "Craig Federighi", emailAddress: "craig@apple.com"),
      PaymentsContact(name: "Phil Schiller", emailAddress: "phil@apple.com"),
    ]
  }

We’ll also need a way to get an INPerson object from our contacts. This can be wrapped up in a simple instance function on PaymentsContact:

public func inPerson() -> INPerson {
    let nameFormatter = PersonNameComponentsFormatter()

    if let nameComponents = nameFormatter.personNameComponents(from: name) {
      return INPerson(handle: emailAddress, nameComponents: nameComponents, contactIdentifier: nil)
    }
    else {
      return INPerson(handle: emailAddress, displayName: name, contactIdentifier: nil)
    }
  }

Now we can check our PaymentsContact objects against the contacts provided by Siri inside SendPaymentIntentHandler. The INSendPaymentIntentHandling protocol defines another method for resolving the payee of a transaction, in the form of resolvePayee(intent:completion:). This is where the app will take the name provided by Siri and determine what to do with it.

Here’s a basic first pass of the resolvePayee function:

func resolvePayee(forSendPayment intent: INSendPaymentIntent, with completion: (INPersonResolutionResult) -> Swift.Void) {
    if let payee = intent.payee {
      let contacts = PaymentsContact.allContacts()
      var resolutionResult: INPersonResolutionResult?
      var matchedContacts: [PaymentsContact] = []

      for contact in contacts {
        print("Checking '\(contact.name)' against '\(payee.displayName)'")

        if contact.name == payee.displayName {
          matchedContacts.append(contact)
        }
      }

      switch matchedContacts.count {
      case 2 ... Int.max:
        let disambiguationOptions: [INPerson] = matchedContacts.map { contact in
          return contact.inPerson()
        }

        resolutionResult = INPersonResolutionResult.disambiguation(with: disambiguationOptions)
      case 1:
        let recipientMatched = matchedContacts[0].inPerson()
        print("Matched a recipient: \(recipientMatched.displayName)")
        resolutionResult = INPersonResolutionResult.success(with: recipientMatched)
      case 0:
        print("This is unsupported")
        resolutionResult = INPersonResolutionResult.unsupported(with: INIntentResolutionResultUnsupportedReason.none)
      default:
        break
      }

      completion(resolutionResult!)
    } else {
      completion(INPersonResolutionResult.needsValue())
    }
  }

You’ll notice that the completion handler is responsible for telling Siri how the request was handled. We can tell Siri that the request was unsupported, or that we need a proper value, or even that we found multiple matches and that there is clarification needed.

Handling Siri Requests In Your App

Not all requests can or should be handled by Siri. Perhaps, for a payments app, you would only want to automatically handle small payments to trusted contacts. If somebody picks up your unlocked phone, they shouldn’t be able to transfer $1000 to themselves with no authentication.

Instead, Siri can be used to start a transaction and then hand that off to the app for completion, where you can authenticate the request fully and verify that it should go through.

Telling Siri to take the user to your app is pretty easy. You do this by providing an NSUserActivity object to the handle completion block. I added this chunk of code into that function:

let userActivity = NSUserActivity()
completion(INSendPaymentIntentResponse.init(code: .success, userActivity: userActivity))

Then, in your App Delegate, you can handle the user activity like so:

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
    if let interaction = userActivity.interaction, let intent = interaction.intent as? INSendPaymentIntent, let payee = intent.payee {
      print("Paying \(payee.displayName) \(intent.currencyAmount!.amount!)")
    }

    return true
  }

Displaying an Interface with Siri

Last of all, you will want to display a custom user interface to users to let them know they’ve paid somebody through your app. Instead of using the default Siri payments UI, you can liven things up by providing your own colors and icons.

The only catch is that you can’t use anything interactive; buttons and gesture recognizers won’t work. If you provide the user something to tap on, they might try and tell Siri to do what the button says, leading them to think it’s broken or not doing what they want. Instead, Siri handles all user interaction and forwards it onto your UI.

To add a custom UI, add an extension the same way you added the basic Intent support, but choose Intent UI instead. All you then need to do is modify the newly created Info.plist to have the INSendPaymentIntent item (removing the default workout intents).

After that, it’s up to you to customise the IntentViewController. The INUIHostedViewControlling protocol is how Siri passes information into this class, which you can receive through the configure delegate method. Here’s a basic implementation:

func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
    if let sendIntent = interaction.intent as? INSendPaymentIntent {
      if let payee = sendIntent.payee {
        // Do something with payee.displayName
      }

      completion(self.desiredSize)
    }
    else {
      completion(self.desiredSize)
    }
  }

desiredSize is a function provided by the boilerplate class generated when you added this extension; it works by returning a CGSize. You can provide self.extensionContext!.hostedViewMaximumAllowedSize to give the largest size allowed, or the equivalent hostedViewMinimumAllowedSize for the opposite. On an iPhone 6, this gives a height range of 120 points up to 200 points. Width scales to the size of the device.

Conclusion

There’s still a lot to learn with SiriKit. I haven’t yet tested out sharing data between Siri extensions and the core app, or done a lot with handing off an interaction from Siri to the app, but this is still a fresh API. I’ll likely update the demo app as I continue to learn more, so check back on that if you’re curious to see how SiriKit works a little more in-depth.

You can get the source code for this article on GitHub.

Linting Changed Files With SwiftLint

SwiftLint is an incredible tool for identifying style issues with Swift codebases, and highlighting them right in Xcode. It’s great for saving time during code reviews, letting reviewers to worry about important issues.

The problem is trying to integrate SwiftLint into an existing codebase which has been worked on by dozens of authors – adding SwiftLint into a work project gave us over 20,000 warnings. It’s fantastic that SwiftLint caught these, but this drowns out issues which may be really important.

One way around this problem is to only check files which have been modified. Our scripts are kept in a Scripts directory, so this was added as a Run Script build phase:

${SRCROOT}/Scripts/lint.sh

Next, the script itself:

SWIFT_LINT=/usr/local/bin/swiftlint

lint() {
  local filename="${1}"
  if [[ "${filename##*.}" == "swift" ]]; then
    ${SWIFT_LINT} lint --path "${filename}"
  fi
}

if [[ -e "${SWIFT_LINT}" ]]; then
  git diff --cached --name-only | while read filename; do lint "${filename}"; done
else
  echo "SwiftLint is not installed."
  exit 0
fi

This uses git to get the file names of the changed files and check that they’re Swift source files. If so, they get run through swiftlint and any warnings are sent to Xcode. Now you can still get warnings for files you’re working on, without the Issues navigator becoming too crowded.

Exploring Swift, Part 1: Getting Started

With the open source release of Swift in December last year, developers everywhere have been able to delve into the code behind the language and help improve it for everybody else. They’ve been able to help track down bugs in the current releases, and also help plan out future iterations.

This has been fantastic for those of us who work with Swift daily, and it would be great to be able to help contribute to the language as well. As it turns out though, programming languages are complicated. Contributing to Swift can be tough without having the time to really learn how it works under the hood. I wanted, for my benefit and for the benefit of others, to start studying the Swift source code and writing up the process as I go.

I’m learning how Swift works as I go, so some (or all) of this will likely be wrong. Sorry!

Cloning the Project

The Swift developers include superb documentation for getting up and running with Swift in their GitHub repo. Assuming you’re on OS X and have an SSH key added to your GitHub account, setting up can be done by running a couple commands:

git clone git@github.com:apple/swift.git
cd swift
./utils/update-checkout --clone-with-ssh

This will use Swift’s install scripts to update the project and also clone its dependencies, such as LLVM. These dependencies will end up in the same directory level as your Swift repo itself, so it may be worth cloning Swift into its own parent directory in order to keep everything contained.

You can also re-run the update-checkout --clone-with-ssh command later to bring everything up to date.

Running the Tests

A good first start is to run Swift’s tests. Apple again provides an excellent guide on how to do this.

The basic set of tests can be run with ./utils/build-script --test. This process takes a little while, but it will build Swift and its dependencies before running through the test suite, giving you progress along the way. At the end, you get a report:

Testing Time: 990.31s
  Expected Passes    : 2716
  Expected Failures  : 6
  Unsupported Tests  : 47
-- check-swift-macosx-x86_64 finished --
--- Finished tests for swift ---

I’ll revisit the test suite later and explore how it’s set up, as well as how the tests themselves are structured.

Editing With Xcode

You’ll likely want to edit the source code with Xcode; you can build an Xcode project for Swift by running utils/build-script -x. This will build a project for you in the build directory (one level up from the Swift source code repo).

It won’t surprise you that there is a lot of stuff in here, so using the fuzzy finder (Command+Shift+O) to find the classes you want is the way to go.

Swift running in Xcode

Breaking the Tests

Alright! With the boring stuff out of the way, it’s time to make a change to one of the test files and see what an intentional failure looks like. Breaking existing tests is the first step to writing new ones, so let’s get going.

I’m going to pick on the reverse function. There is a test file named CheckSequenceType.swift (in swiftStdlibCollectionUnittest-macosx-x86_64) which houses a variety of tests for collections. In here, you’ll find some tests for reverse and friends.

public let reverseTests: [ReverseTest] = [
  ReverseTest([], []),
  ReverseTest([ 1 ], [ 1 ]),
  ReverseTest([ 2, 1 ], [ 1, 2 ]),
  ReverseTest([ 3, 2, 1 ], [ 1, 2, 3 ]),
  ReverseTest([ 4, 3, 2, 1 ], [ 1, 2, 3, 4]),
  ReverseTest(
    [ 7, 6, 5, 4, 3, 2, 1 ],
    [ 1, 2, 3, 4, 5, 6, 7 ]),
]

Try breaking one of these tests:

ReverseTest([ 3, 2, 1 ], [ 3, 2, 1 ]),

Since this is a validation test, ./utils/build-script --validation-test will rebuild and test the suite. So what happens?

[ RUN      ] Sequence.reverse/Sequence
check failed at /Users/sasymons/Code/OSS/Swift/swift/stdlib/private/StdlibCollectionUnittest/CheckSequenceType.swift, line 759
stacktrace:
  #0: /Users/sasymons/Code/OSS/Swift/build/Ninja-DebugAssert/swift-macosx-x86_64/validation-test-macosx-x86_64/stdlib/Output/SequenceType.swift.gyb.tmp/Sequence.swift:752
expected: [2, 1] (of type Swift.Array<Swift.Int>)
actual: [1, 2] (of type Swift.Array<Swift.Int>)
[     FAIL ] Sequence.reverse/Sequence
Sequence: Some tests failed, aborting
UXPASS: []
FAIL: ["reverse/Sequence"]
SKIP: []

As expected, the tests fail and even provide the offending assertion. Not only that, but the test output will provide a command to run to isolate the failure and fix it. In my case, this is:

/Users/sasymons/Code/OSS/Swift/build/Ninja-DebugAssert/swift-macosx-x86_64/validation-test-macosx-x86_64/stdlib/Output/SequenceType.swift.gyb.tmp/a.out --stdlib-unittest-in-process --stdlib-unittest-filter "reverse/Sequence"

Undo the change to the reverse tests, rebuild Swift, and then run this command to see the tests pass once again. Phew!

Wrapping Up

Give its complexity, Swift seems very open to new contributors. The scripts are friendly, stable, and there is plenty of documentation on GitHub to get started with.

This is just the beginning, so with the initial introduction to the build system out of the way, we can start looking at how Swift really works.

Writing an OS in Rust

Fantastic series from Philipp Oppermann on building an operating system from scratch using assembly and Rust.

← Previous Page