Active Filters: Tools

Today we'll take our first step towards contributing to the Swift language. We'll learn how to download the codebase, compile it, and run tests. Let's get started!

First, we'll prepare our environment. We'll be doing all of this on a Mac today, but all of this is also possible on Linux (and a bunch of other platforms).

We'll begin by using homebrew to install a couple of build tools:

brew install cmake ninja

Once that's done, we'll make a new directory somewhere sensible where we can put everything:

mkdir -p ~/projects/swift

Next, we'll do the first clone:

git clone git@github.com:apple/swift.git

Once this is done, we'll pull down all the dependencies and other projects involved with building Swift:

./swift/utils/update-checkout --clone-with-ssh

We have now cloned and checked out a lot of code, it's time to get building. We'll head into the swift directory and kick off a standard build, then run basic tests like this:

cd swift
./utils/build-script -r -t

That's it! At this point we can go get a cup of coffee (no really, this is going to take a while, even on a fast machine).

With these skills in hand though, we can now edit code, and see what effects our changes have on tests.

We can run basic tests at anytime with:

./utils/build-script --test

Or run more intensive validation tests like this:

./utils/build-script --validation-test

This is just the beginning, in the future we'll look at fixing a Swift bug, and contributing our work back to the project.

The Swift language is always evolving. Bugs are being fixed, new proposals are implemented, etc. It'd be great if we could try out these features out as they're being implemented, without having to wait for official releases.

Today we'll take a look at how to download, install, and try out the latest Swift Toolchain in Xcode. Let's get started!

We'll begin by heading to https://swift.org/download/ and looking for the "Snapshots" section. Here we can also find many other preview releases, etc.

Click "Xcode" to download an installer:

We'll run open the installer, and complete its steps.

We can now open Xcode and select our new Toolchain.

We can also manage Toolchains in Xcode's preferences:

With our new Toolchain selected, all builds will use it, neat!

Getting involved with Swift's development is a great way to stay informed on where things are headed. We'll look at getting even more involved in future Bites!

WWDC brought us a whirlwind of changes all over Apple's platforms. One interesting announcement was Xcode Source Editor Extensions. These allow us to add commands to Xcode that can work with the contents of the code we're working on. Yes, Apple also announced that Xcode won't load plugins (like Alcatraz, etc.) anymore. Bummer.

Today we'll try to shake off our feels about plugins going away by making our first Source Editor Extension, let's do it!

We're going to make a super-simple Extension that lets us easily "sign" our code comments. Like this:

// - @jakemarsh

We'll start by creating a new macOS app. Then we'll add a new target to it, and choose Xcode Source Editor Extension.

Xcode gives us a nice template of files to start with. We can head into SourceEditorCommand.swift to implement our command.

class SourceEditorCommand: NSObject, XCSourceEditorCommand {
  func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: (NSError?) -> Void ) -> Void {
    guard let selection = invocation.buffer.selections.firstObject as? XCSourceTextRange else { completionHandler(createError()); return }

    let commentText = "// - @\(NSUserName())"

    invocation.buffer.lines.insert(commentText, at: selection.end.line)

    completionHandler(nil)
  }
}

We start by looking at the invocation's selection and guard'ing to make sure there's a valid insertion point for us to append to, then create a new comment using the handy NSUserName() function in macOS.

Finally, we use the provided XCSourceEditorCommandInvocation one more time to insert the new comment text into the buffer. We call the completionHandler with a nil to let Xcode know we've completed our work without any errors, and we're done!

We can customize the name of our command in our Info.plist, like this:

Now, how are we going to test this thing out? Well, Xcode has some nice support built in for debugging these, but first we'll have to endure a little pain to get things working (at least during this first beta, later Xcode 8 releases will hopefully not need these steps).

We'll only need this while running OS X 10.11, macOS 10.12 users can skip this step:

We'll need to run sudo /usr/libexec/xpccachectl in Terminal, then reboot our Mac.

Once it's back up, we can open Xcode 8 again, and Build & Run our Extension. We'll be asked to choose which app we'd like to run it in. We'll choose Xcode 8 (be careful not to choose an older Xcode version as the icons are easy to miss).

Another instance of Xcode 8 will launch in our dock and… Boom! Xcode's dark heart is revealed!

Just kidding, Xcode is displaying itself this way to help us differentiate between the one that we're debugging, and the "real" one.

We can head to the Editor menu to test out our new command. That's not all though, we can go back to the other Xcode instance and set breakpoints, inspect variables, etc. Neat!

Plugins may be gone, but Source Editor Extensions still offer a ton of interesting possibilities. Looking for inspiration? Here's a few on Github already.

Plugins are a great way for us to extend Xcode, adding functionality that Apple didn't ship with it. We've covered Xcode plugins a bit here in the past, notably in Bite #147, where we learned about using Alcatraz to install plugins. Today we'll look at a new plugin called BuildTimeAnalyzer from Robert Gummesson. It can help uncover why are code is compiling slowly. Let's dive in.

We'll start by completing the steps in Bite #147 to install Alcatraz. (If we're going to be installing and trying out plugins Alcatraz is a good way to organize and manage everything).

Once installed, we'll open the Alcatraz Package Manager by going to Window > Package Manager.

We can search for "BuildTimeAnalyzer" in the search field to find the plugin and install it. Once installed, we'll restart Xcode and open our project.

We can open the analyzer window by selecting View > Build Time Analyzer from Xcode's menu.

Next, we'll need to add some compiler flags.

(Remember to add these to any other targets your app may depend to see compile times for those too).

We'll add -Xfrontend and -debug-time-function-bodies to the "Other Swift Flags" in our app's target's build settings.

After that, one last clean and build and we should start to see a list of how long each bit of our code is taking to compile. Neat!

We can click on each result to jump straight to the line of code that's causing the slow down. The results are often surprising!

For example: Adding Collection types together seems to bring the Swift compiler to a slow crawl.

What interesting slow downs did you find in your code? Send them to @lilbitesofcocoa on Twitter!

More info about BuildTimeAnalyzer can be found at git.io/bta

It's another fastlane Friday!

Bitcode is awesome. It lets our users download much smaller versions of our apps, containing only the bits they need to run it on their devices.

Unfortunately, it creates a few challenges for using third-party crash-reporting services.

Not to worry, fastlane has our backs. Today we'll look at a new built-in fastlane action (Bite #140) that can automate all of this.

The issue is that Crashlytics (or any service analyzing our crash logs) needs our dSYM files to convert raw crash logs to ones that containing line numbers and function names and all the other useful information we'll need to actually fix the bug causing the crash.

We'll begin by making sure we have the latest version of fastlane. (A quick gem update fastlane will do the trick).

Then, we'll add a new lane:

lane :refresh_dsyms do
  download_dsyms                  # Download dSYM files from iTunes Connect
  upload_symbols_to_crashlytics   # Upload them to Crashlytics
  clean_build_artifacts           # Delete the local dSYM files
end

Now we can just run fastlane refresh_dsyms at anytime and everything will be synced up to Crashlytics. Apple seems to recompile our apps at will, and whenever their systems need.

For these reasons, it's probably best to toss this command into a CI server or some other system that will run it once or twice a day.

Support is already in for Crashlytics, Sentry and HockeyApp.

Everything is open source, so we can add support for our own service if needed.

More info about all of this can be found in fastlane's docs, found here.

Those who have been an iOS, OS X, tvOS, or watchOS developer for any significant amount of time know that Xcode is an incredible tool for creating awesome apps. Until it isn't. Sometimes Xcode gets confused and needs our help. Today we'll look at one way we can clean up our environment and possibly get back to work.

There's a myriad of reasons why the following technique resolves many common Xcode issues. The important part is that it works. Sometimes. Maybe.

Anyways, there's this folder full of "invariants" and temporary files that Xcode shuffles things around in as we use it to build our app. It's called DerivedData and contains caches, compiler waste, build products, etc.

If we ever find ourselves wondering if a certain compiler error or unexpected Xcode behavior really is our fault or a bug in Xcode, we can try clearing out this folder before jumping on the Google train.

The "manual" quick/dirty way is to quit Xcode then run:

rm -rf ~/Library/Developer/Xcode/DerivedData

Then re-launch Xcode. Also keep those fingers crossed.

Alternatively, we could use an app to do all this for us. Watchdog is an app that lives in our menu bar and cleans up stale cache files in Xcode. Very cool!

When Swift debuted, we said goodbye to using #pragma pre-processor definitions to organize our code. Don't worry, Xcode still has our backs. We can use a few different "special" comments in our code and Xcode will pick up on them and display them in its jump bar:

The extra dash character in the name of our MARK gets us those sweet separators in the source navigator dropdown.

Xcode also supports a similar bit of functionality for TODO and FIXME comments. They'll show up in bold in the same dropdown.

func launch() {
  // TODO: launch here
}

func land() {
  // FIXME: shouldn't crash
}

As a bonus, we can use a little regex magic to get Xcode to generate build warnings for TODO and FIXMEs. We'll just add a new Run Script build phase to our project that contains:

KEYWORDS="TODO|FIXME|\?\?\?:|\!\!\!:"
find "${SRCROOT}" \( -name "*.swift" \) -print0 | \
xargs -0 egrep --with-filename --line-number --only-matching "($KEYWORDS).*\$" | \
perl -p -e "s/($KEYWORDS)/ warning: \$1/"

Shout out to Jeffrey Sambells for originally sparking this idea!

Following up on Bite #201 about reporting bugs to Apple, today we'll take a look at a tool that makes reporting bugs even easier called QuickRadar. It's great and will give us a chance to discuss one more piece of this puzzle, OpenRadar.

QuickRadar is a open-source menu bar app for OS X that simplifies and improves upon the "traditional" bug reporting process we looked at in Bite #201. We can download it for free from quickradar.com. We'll move it over to /Applications, then start it up.

We can head over to QuickRadar's preferences to fill in our Apple ID credentials.

Next, we'll select New Radar... to see the app's main interface. Here we can describe our bug, drag in a sample project, etc.

We'll see another option at the bottom: "Send to Open Radar". Open Radar is a site run by the community that catalogs and allows us to search/compare bugs with each other.

Usually we have to post them there manually, but with QuickRadar, we'll just add our credentials in preferences, then check the box. Finally, to ease confusion, links like rdar://12345 only work for Apple employees.

QuickRadar is available at quickradar.com

Open Radar is at openradar.me

.stringsdict files were added in iOS 7 and OS X 10.9. They allow us to "pluralize" text in our app without a bunch of switch or if statements.

let spaceships = [Spaceship(selected: true)]

let pluralized = String.localizedStringWithFormat(
  NSLocalizedString("%d ships selected",
    comment: "how many ships selected"),
  spaceships.filter { $0.selected }.count
)

The localizedStringWithFormat function is terribly smart.

It will look for a Localizable.stringsdict file before returning and query it for the correct format string to use before returning our final formatted string.

A Localizable.stringsdict is really just a .plist file with a few special strings in it.

We can create a Localizable.stringsdict file either completely by hand in a text editor, or by adding a new Property List to our project and naming it Localizable.stringsdict.

Now that we have a .stringsdict file, let's use it.

The root key is the same one we passed into NSLocalizedString.

Then, we define a sort of string-replacement-variable using some special characters: %#@VARIABLE_NAME_HERE@. The system will then find the sub-dictionary that shares a name with this variable, and use it to pluralize.

We can provide cases for zero, one, two, few, many, and other. Each potential plural case is optional for each language, except other.

Download an example project showing off this functionality here.

Hat tip to Matthew Bischoff for the idea for today's Bite, which actually inspired a whole series of localization Bites with more still to come!

Today we'll continue our localization series with a lesser-known Xcode feature that makes the process of translating our apps easier. Usually we won't be translating our apps ourselves, at least not into all the languages we want to support.

It's common to send our strings off to a third-party service, who will translate them and return them to us. Let's get started.

In Bite #181, we enabled the basic localization features of Xcode. As we work on our app, we'll add new strings:

button.setTitle(
  NSLocalizedString("fly-here", comment: "Fly Here"),
  forState: .Normal)

When we do, we could manually add the new key to all of the different language versions of our Localizable.strings file.

We could also generate a brand new Localizable.strings file using Apple's genstrings command-line tool. Running gentrings *swift in our project's root directory will scan our code and generate a new Localizable.strings file with all the NSLocalizedString keys we've used.

That sounds like it could lead to lots of manual diff'ing.

It turns out that Xcode itself actually has a more powerful feature that can export our strings for translation with almost no manual labor. It pulls from all our Storyboards, .xibs, and scans our code for NSLocalizedStrings.

To use it, we'll select our project at the top of the Project Navigator, and then select Editor > Export for Localization... This will export a folder full of .xliff files for all the languages we've added to our project in Xcode.

This format is standard for most translation services. We'll find a service, then upload these files to them. They'll return us a similar set of files, filled with our translated strings. When they do we'll select Editor > Import Localizations... option then import them back into Xcode.

Popular translation services like Babble-on and Applingua specialize in mobile apps and support .xliff files. We could also edit the files ourselves using a tool like iXLIFF.

Page 2 of 6