Calling Go code from Swift on iOS and vice versa with Gomobile

Mat Ryer
Mat Ryer
Jul 22, 2017 · 11 min read

Gomobile lets you turn a Go package into a Framework that can be imported into a Swift application which allows you to call Go code from Swift, and vice versa.

Most online tutorials focus on calling Go from Swift, and not the other way round but if we are to really use Go to build out the bulk of our iOS applications, this is a vital ingredient. It took me several hours to get it right thanks to the temperamental nature of Xcode mixed with my lack of understanding of Gomobile, so this tutorial presents a solution that is repeatable (by me) and hopefully useful to you too.

This tutorial will cover:

  • Common errors or unexpected blockers (search for GOTCHA throughout this post to highlight these), and how to overcome them
  • Calling a Go constructor in Swift to create an instance of a Go struct
  • Define an interface in Go that we want our Swift code to implement
  • Pass the Swift implementation to the Go constructor, and initiate two-way communication

You will need:

Structure

Let’s start with a simple folder structure so we can discuss the constituents of our project.

Somewhere inside your $GOPATH, create a folder called gomobiletest and add the following subfolders:

  • The frameworks folder is where we’ll put the framework that Gomobile generates for us
  • The go folder will contain our Go code, and the greeter package is where our business logic will live
  • The ios folder is where we’ll create our Swift project later

In this tutorial, we are going to build an iOS Framework from our greeter package.

Our Go package

Let’s start by building a simple Go package that lets us greet somebody.

We will write:

  • A constructor to create and return a struct type
  • An interface for printing values, which we’ll implement in Swift
  • A Go method on the struct that prepares the greeting and calls the method on the interface to print it out

To gomobiletest/go/greeter, add the following greeter.go file:

GOTCHA: Originally I named my Printer method Print, but once this gets translated into Swift it clashes with the builtin print function, and I ended up recursing forever. SOLUTION: Be careful with names; they might have to be slightly more verbose (PrintSomething vs Print) than we would like in traditional Go code so it doesn’t clash in Swift.

The above code is fairly simple; we create a Greeter with the NewGreeter function, taking in a Printer which we use later when sending the greeting. In Go we might omit the NewGreeter constructor, but we can’t in Gomobile because Swift code cannot instantiate Go objects.

In Gomobile, all memory allocation of the types must occur in the world where that type is defined.

Building the framework

Now our package is ready to go, it’s time to build the iOS framework that can be imported into our Swift project.

We are going to use the gomobile command to do this, specifically its bind feature. In a terminal, navigate to the gomobiletest/go/greeter folder and use the gomobile command to create the framework:

After a few moments, you will notice that a new item has appeared in our frameworks folder called Greeter.framework.

You’ll notice that an iOS framework is actually just a folder full of stuff, including the Greeter binary. Take a moment to check out what was generated:

Peek at the Objective-C headers

Let’s have a quick look at the Objective-C headers for our framework (remembering that we’re not going to be using these). Open the Greeter.objc.h file:

This generated code describes the Objective-C objects that represent our Go greeter package. Notice specifically:

  • @protocol GreeterPrinter— this is our Printer interface represented as a protocol

Did you know that protocols in Objective-C are actually the precursor to interfaces in Go? — Blaine Garst, who I have the privilege of calling a friend, cooked-up protocols (with Bertrand Serlet and Steve Naroff) when they worked at NeXT in 1990. You can read more about that in A Short History of Objective-C by Hansen Hsu.

  • - (void)printSomething:(NSString*)s— This line inside the protocol describes our PrintSomething method; the cases are mutated to match the iOS patterns
  • FOUNDATION_EXPORT GreeterGreeter* GreeterNewGreeter(id<GreeterPrinter> printer)— This describes our NewGreeter constructor function

You will notice that all types are in the global namespace (rather than being encapsulated into a package) and are instead just prefixed with the package/framework name. This is the same in Swift so you should bear this in mind when naming things, and be sure to avoid clashes.

GOTCHA: Your package name must match the name of your framework. So package greeter means our framework must be called Greeter.framework. Originally my package had a different name and it was failing without telling me why.

Create an iOS application

Now open Xcode and choose Create a new Xcode project. In the iOS tab, select Single View Application and click Next.

Be sure to select Swift as the language, and fill out the rest of the fields as you see fit. Mine looks like this:

Give your project a name (let’s stick with GoMobileTester for now) before finally clicking Next and then Create once you’ve navigated to the ios folder.

Importing a Gomobile framework

Now we are going to import our Greeter.framework so that we can interact with it in Swift. Open Finder, and navigate to the frameworks folder. Then drag the Greeter.framework file into Xcode at the bottom of the Project navigator. A dialog will open to confirm the action, be sure to check Copy items if needed.

GOTCHA: Some tutorials say that copying the framework is optional, but I couldn’t get it to work without doing so — let me know if you learn differently.

Click Finish and the framework should be added:

You will also notice that the framework has been added to the Link Binary With Libraries section of the Build Phases tab:

This is really what tells Xcode to bundle the framework with the iOS application.

Implementing a Go interface in Swift

Now that the framework is imported, let’s write the Swift implementation of the Printer interface we defined in Go.

Right-click the GoMobileTester folder (not the Xcode project at the top, the one below that), and choose New File... Select Cocoa Touch Class, and click Next. Call the class SwiftPrinter and make sure it’s a subclass of NSObject and click Next and then Create.

The skeleton class that Xcode creates for us looks like this:

The first thing we are going to do is inform Swift that this object is going to implement the protocol (our Go Printer interface).

Update the code to import the Greeter framework and add the protocol to the list of things the SwiftPrinter inherits:

GOTCHA: The Objective-C objects actually has different names to the Swift objects, so if you’re using Swift, it’s better to completely ignore those Header files we looked at earlier, because they’ll only confuse things.

The actual protocol name is GreeterPrinterProtocol. The import Greeter line at the top ensures that this code file can access the objects inside the Greeter framework. Without it, we’d see an error complaining that it doesn’t know what GreeterPrinterProtocol is.

Inspect the generated Swift code

While holding Cmd+Shift, click on the GreeterPrinterProtocol protocol in the code and notice that we are taken to a new file that contains the Swift code. This is the Swift equivalent of the Objective-C code we looked at earlier, although the names are different:

Hit Cmd+B to build the project, and notice that we encounter an error:

It tells us that the SwiftPrinter does not conform to our protocol; which is fair enough.

Update the code adding the printSomething function:

Build again (press Cmd+B) and notice that there are no errors.

Calling Go code from Swift

Now it’s time to call our Go constructor from Swift. The simplest place to do this is in the ViewController.swift file, just after the default view is loaded.

First, add the same import to the top of the file:

Next, inside the viewDidLoad function, let’s first prepare the Printer argument that our NewGreeter function expects:

So the printer variable will be an instance of the SwiftPrinter we just made.

Now let’s create a Greeter (actually in Swift it’s a GreeterGreeter):

So our greeter variable will be an instance of the Greeter type (that we wrote in Go) and the Printer interface will be satisfied by our printer variable, which is an object that implements the appropriate protocol.

Finally, let’s just call the greet method:

Step-by-step, here’s what’s going on:

  • Swift: iOS application calls viewDidLoad
  • Swift: We create a SwiftPrinter, and use GreeterNewGreeter to create a new Greeter, passing in the printer
  • Go: NewGreeter creates a new Greeter object (saving the printer as a field so we can access it later), and returns it
  • Swift: We save the returned GreeterGreeter in the greeter variable (strange name; remember it’s the package name and the struct name combined)
  • Swift: We call the greet method on the greeter, passing in a name
  • Go: Accessing the printer, we call the PrintSomething method, by concatenating Hello and the passed-in name
  • Swift: The SwiftPrinter type’s printSomething method is called, where we print out This just in: with the greeting string
  • Finally, the string should be printed to the console via the built-in print method

No Bitcode

Press Cmd+B again and notice that we get an error.

Thanks Xcode, that’s very easy to deal with!

Seriously, this is a great example of why Go and its toolchain win over Swift and its toolchain in my opinion — although you do get used to Xcode the more you work with it.

If you read carefully and haven’t already taken your own life, you will notice that it says this:

Gomobile doesn’t support Bitcode right now, so we will take this advice and go and disable it.

GOTCHA: You must disable Bitcode for any application that imports a Gomobile framework.

In the Project navigator, select the topmost GoMobileTester project item, and select the Build Settings tab. Type bitcode in the search bar and change the Enable Bitcode setting to No:

Build again by pressing Cmd+B and notice this time that it builds successfully.

Running the solution

Select an iPhone simulator from the toolbar (top left) and press the play (run) button.

After a moment or two, the simulator will startup and the code will execute.

Click the middle button from the group of window controls in the top right of Xcode to open the console:

We did it

Eventually you’ll see this message in the console in Xcode:

We have just successfully called Go code from Swift, and had a Swift method called by Go. 👍

Conclusion

We have successfully solved two-way communication between Swift and Go. Which means, in theory, you can build any kind of application you like.

But be careful, Gomobile is still very young and there are some limitations that you need to be aware of as you design your solution.

The remainder of this article will discuss limitations, as well as other ideas that might increase the chances of success.

Not all types are supported

Currently some types (like slices) are not supported by Gomobile, so you’ll have to wrap things in structs and methods. This is a pain, because your Go code ends up looking very non-Go, and if you have other customers for your code, you might end up with a pretty ugly and hard to use package.

It is assumed that slices will end up being supported by Gomobile, so this might be a temporary thing.

Bridging?

You might consider writing a bridge package that sits between your idiomatic Go code, and your Swift code. This bridge package would become the framework that gets imported.

In the bridge package, you could present the bare minimum capabilities that you want including in your package, and don’t have to worry about unsupported features from the rest of the code.

And you could build some method based slice abstraction without ruining your original Go code.

Keep up Xcode

Xcode tries to help keep things quick by caching a lot. This means, that if you change your framework API (say you rename the Greet method to SendGreeting) you might struggle to get Xcode to recognise the changes. I managed to get around this by creating a new Xcode project each time, but obviously as you get further down a project, that’s not a great solution.

You might want to try updating the framework in place.

Update in place

Instead of having a frameworks folder at all, you could ask the gomobile tool to put the output directly into the Xcode project. You can do this just by figuring out the appropriate path, like this:

This way, whenever you rebuild your framework, it’ll update the files directly.

What next?

Mat Ryer

Written by

Mat Ryer

Founder at MachineBox.io — Gopher, developer, speaker, author — BitBar app https://getbitbar.com — Author of Go Programming Blueprints

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade